From 4d801520ab22c3b421582c7ef03a20b432b26747 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 15 Dec 2025 21:51:12 +0000 Subject: [PATCH 01/72] patch: fix https://github.com/PyLops/pyproximal/issues/230 --- pyproximal/proximal/L1.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyproximal/proximal/L1.py b/pyproximal/proximal/L1.py index 6849d74..6fbf6bc 100644 --- a/pyproximal/proximal/L1.py +++ b/pyproximal/proximal/L1.py @@ -51,13 +51,17 @@ class L1(ProxOperator): r"""L1 norm proximal operator. Proximal operator of the :math:`\ell_1` norm: - :math:`\sigma\|\mathbf{x} - \mathbf{g}\|_1 = \sigma \sum |x_i - g_i|`. + + - :math:`\sigma\|\mathbf{x} - \mathbf{g}\|_1 = \sigma \sum_i |x_i - g_i|` for + 1-dimensional arrays; + - :math:`\sum_j \sigma_j \|\mathbf{X}_j - \mathbf{g}\|_1 = \sum_j \sigma_j \sum_i |x_{i,j} - g_i|` + for 2-dimensional arrays. Parameters ---------- sigma : :obj:`float` or :obj:`np.ndarray` or :obj:`func`, optional Multiplicative coefficient of L1 norm. This can be a constant number, a list - of values (for multidimensional inputs, acting on the second dimension) or + of values (for 2-dimensional inputs, acting on the second dimension) or a function that is called passing a counter which keeps track of how many times the ``prox`` method has been invoked before and returns a scalar (or a list of) ``sigma`` to be used. @@ -117,8 +121,8 @@ def __init__( def __call__(self, x: NDArray) -> float: sigma = _current_sigma(self.sigma, self.count) if self.g is None: - return float(sigma * np.sum(np.abs(x))) - return float(sigma * np.sum(np.abs(x - self.g))) + return float(np.sum(sigma * np.sum(np.abs(x), axis=0))) + return float(np.sum(sigma * np.sum(np.abs(x - self.g), axis=0))) def _increment_count(func: Callable[..., Any]) -> Callable[..., Any]: """Increment counter""" From 522012b23d82c7405b81b18e0e412c425d1d8a0a Mon Sep 17 00:00:00 2001 From: Toru Tamaki Date: Thu, 25 Dec 2025 19:01:00 +0900 Subject: [PATCH 02/72] add PPXA and ConsensusADMM algorithms with corresponding documentation and tests - Parallel Proximal Algorithm (PPXA) for minimizing the sum of proximable functions - ConsensusADMM for solving the global consensus problem with ADMM - add tests in test_solver.py - test_ConsensusADMM_with_ADMM - test_ConsensusADMM_with_ADMM_for_Lasso - test_ConsensusADMM_with_GPG - test_PPXA_with_ADMM - test_PPXA_with_GPG --- docs/source/api/index.rst | 2 + pyproximal/optimization/__init__.py | 2 + pyproximal/optimization/primal.py | 292 ++++++++++++++++++++++++++++ pytests/test_solver.py | 221 +++++++++++++++++++++ 4 files changed, 517 insertions(+) diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 7184220..ddf5c47 100755 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -171,6 +171,8 @@ Primal ProximalPoint TwIST DouglasRachfordSplitting + PPXA + ConsensusADMM .. currentmodule:: pyproximal.optimization.palm diff --git a/pyproximal/optimization/__init__.py b/pyproximal/optimization/__init__.py index 3e7e555..694aac1 100644 --- a/pyproximal/optimization/__init__.py +++ b/pyproximal/optimization/__init__.py @@ -20,6 +20,8 @@ TwIST Two-step Iterative Shrinkage/Threshold PlugAndPlay Plug-and-Play Prior with ADMM DouglasRachfordSplitting Douglas-Rachford algorithm + PPXA Parallel Proximal Algorithm + ConsensusADMM Consensus problem with ADMM A list of solvers in ``pyproximal.optimization.proximaldual`` using both proximal and dual proximal operators: diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index d93db4c..f92885c 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -1793,3 +1793,295 @@ def DouglasRachfordSplitting( print(f"\nTotal time (s) = {time.time() - tstart:.2f}") print("---------------------------------------------------------\n") return x, y + + +def PPXA( # pylint: disable=invalid-name + prox_ops: List[ProxOperator], + x0: NDArray | List[NDArray], + tau: float, + eta: float = 1.0, + weights: NDArray | List[float] | None = None, + niter: int = 1000, + tol: Optional[float] = 1e-7, + callback: Optional[Callable[..., None]] = None, + show: bool = False, +) -> NDArray: + r"""Parallel Proximal Algorithm (PPXA) + + Solves the following minimization problem using + Parallel Proximal Algorithm (PPXA): + + .. math:: + + \mathbf{x} = \argmin_\mathbf{x} \sum_{i=1}^m f_i(\mathbf{x}) + + where :math:`f_i(\mathbf{x})` are any convex + functions that has known proximal operators. + + Parameters + ---------- + prox_ops : :obj:`list` + A list of proximable functions :math:`f_1, \ldots, f_m`. + x0 : :obj:`np.ndarray` or :obj:`list` + Initial vector :math:`\mathbf{x}` of all :math:`f_i` if 1D :obj:`np.ndarray`, + or :math:`\mathbf{x}_{i}` of each :math:`f_i` for :math:`i=1,\ldots,m` + if :obj:`list` or 2D :obj:`np.ndarray`. + tau : :obj:`float` + Positive scalar weight + eta : :obj:`float`, optional + Relaxation parameter (must be between 0 and 2, 0 excluded). + weights : :obj:`np.ndarray` or :obj:`list` or :obj:`None`, optional + Weights :math:`\sum_{i=1}^m w_i = 1, \ 0 < w_i < 1`, + Defaults to None, which means :math:`w_1 = \cdots = w_m = \frac{1}{m}.` + niter : :obj:`int`, optional + The maximum number of iterations. + tol : :obj:`float`, optional + Tolerance on change of the solution (used as stopping criterion). + If ``tol=0``, run until ``niter`` is reached. + callback : :obj:`callable`, optional + Function with signature (``callback(x)``) to call after each iteration + where ``x`` is the current model vector + show : :obj:`bool`, optional + Display iterations log + + Returns + ------- + x : :obj:`numpy.ndarray` + Inverted model + + See Also + -------- + ConsensusADMM: Consensus ADMM + + Notes + ----- + The Parallel Proximal Algorithm (PPXA) can be expressed by the following + recursion [1]_, [2]_, [3]_, [4]_: + + * :math:`\mathbf{y}_{i}^{(0)} = \mathbf{x}` or :math:`\mathbf{y}_{i}^{(0)} = \mathbf{x}_{i}` for :math:`i=1,\ldots,m` + * :math:`\mathbf{x}^{(0)} = \sum_{i=1}^m w_i \mathbf{y}_{i}^{(0)}` + * for :math:`k = 1, \ldots` + + * for :math:`i = 1, \ldots, m` + + * :math:`\mathbf{p}_{i}^{(k)} = \prox_{\frac{\tau}{w_i} f_i} (\mathbf{y}_{i}^{(k)})` + + * :math:`\mathbf{p}^{(k)} = \sum_{i=1}^{m} w_i \mathbf{p}_{i}^{(k)}` + * for :math:`i = 1, \ldots, m` + + * :math:`\mathbf{y}_{i}^{(k+1)} = \mathbf{y}_{i}^{(k)} + \eta (2 \mathbf{p}^{(k)} - \mathbf{x}^{(k)} - \mathbf{p}_i^{(k)})` + + * :math:`\mathbf{x}^{(k+1)} = \mathbf{x}^{(k)} + \eta (\mathbf{p}^{(k)} - \mathbf{x}^{(k)})` + + where :math:`0 < \eta < 2` and + :math:`\sum_{i=1}^m w_i = 1, \ 0 < w_i < 1`. + In the current implementation, :math:`w_i = 1 / m` when not provided. + + References + ---------- + .. [1] Combettes, P.L., Pesquet, J.-C., 2008. A proximal decomposition + method for solving convex variational inverse problems. Inverse Problems + 24, 065014. Algorithm 3.1. https://doi.org/10.1088/0266-5611/24/6/065014 + https://arxiv.org/abs/0807.2617 + .. [2] Combettes, P.L., Pesquet, J.-C., 2011. Proximal Splitting Methods in + Signal Processing, in Fixed-Point Algorithms for Inverse Problems in + Science and Engineering, Springer, pp. 185-212. Algorithm 10.27. + https://doi.org/10.1007/978-1-4419-9569-8_10 + .. [3] Bauschke, H.H., Combettes, P.L., 2011. Convex Analysis and Monotone + Operator Theory in Hilbert Spaces, 1st ed, CMS Books in Mathematics. + Springer, New York, NY. Proposition 27.8. + https://doi.org/10.1007/978-1-4419-9467-7 + .. [4] Ryu, E.K., Yin, W., 2022. Large-Scale Convex Optimization: Algorithms + & Analyses via Monotone Operators. Cambridge University Press, + Cambridge. Exercise 2.38 https://doi.org/10.1017/9781009160865 + https://large-scale-book.mathopt.com/ + + """ + if show: + tstart = time.time() + print( + "Parallel Proximal Algorithm\n" + "---------------------------------------------------------" + ) + for i, prox_op in enumerate(prox_ops): + print(f"Proximal operator (f{i}): {type(prox_op)}") + print(f"tau = {tau:10e}\tniter = {niter:d}\n") + head = " Itn x[0] J=sum_i f_i" + print(head) + + ncp = get_array_module(x0) + + m = len(prox_ops) + if weights is None: + w = ncp.full(m, 1. / m) + else: + w = ncp.asarray(weights) + + if isinstance(x0, list) or x0.ndim == 2: + y = ncp.asarray(x0) # yi_0 = xi_0, for i = 1, ..., m + else: + y = ncp.full((m, x0.size), x0) # y1_0 = y2_0 = ... = ym_0 = x0 + + x = ncp.mean(y, axis=0) + x_old = x.copy() + + for iiter in range(niter): + + p = ncp.stack([prox_ops[i].prox(y[i], tau / w[i]) for i in range(m)]) + pn = ncp.sum(w[:, None] * p, axis=0) + y = y + eta * (2 * pn - x - p) + x = x + eta * (pn - x) + + if callback is not None: + callback(x) + + if show: + if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: + pf = ncp.sum([prox_ops[i](x) for i in range(m)]) + print( + f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} " + f"{pf:10.3e}" + ) + + if ncp.abs(x - x_old).max() < tol: + break + + x_old = x + + if show: + print(f"\nTotal time (s) = {time.time() - tstart:.2f}") + print("---------------------------------------------------------\n") + + return x + + +def ConsensusADMM( # pylint: disable=invalid-name + prox_ops: List[ProxOperator], + x0: NDArray, + tau: float, + niter: int = 1000, + tol: Optional[float] = 1e-7, + callback: Optional[Callable[..., None]] = None, + show: bool = False, +) -> NDArray: + r"""Consensus ADMM + + Solves the following global consensus problem using ADMM: + + .. math:: + + \argmin_{\mathbf{x_1}, \mathbf{x_2}, \ldots, \mathbf{x_m}} + \sum_{i=1}^m f_i(\mathbf{x}_i) \quad \text{s.t.} + \quad \mathbf{x_1} = \mathbf{x_2} = \cdots = \mathbf{x_m} + + where :math:`f_i(\mathbf{x})` are any convex + functions that has known proximal operators. + + Parameters + ---------- + prox_ops : :obj:`list` + A list of proximable functions :math:`f_1, \ldots, f_m`. + x0 : :obj:`np.ndarray` + Initial vector + tau : :obj:`float` + Positive scalar weight + niter : :obj:`int`, optional + The maximum number of iterations. + tol : :obj:`float`, optional + Tolerance on change of the solution (used as stopping criterion). + If ``tol=0``, run until ``niter`` is reached. + callback : :obj:`callable`, optional + Function with signature (``callback(x)``) to call after each iteration + where ``x`` is the current model vector + show : :obj:`bool`, optional + Display iterations log + + Returns + ------- + x : :obj:`numpy.ndarray` + Inverted model + + See Also + -------- + ADMM: Alternating Direction Method of Multipliers + PPXA: Parallel Proximal Algorithm + + Notes + ----- + The ADMM for the consensus problem can be expressed by the following + recursion [1]_, [2]_: + + * :math:`\bar{\mathbf{x}}^{(0)} = \mathbf{x}` + * for :math:`k = 1, \ldots` + + * for :math:`i = 1, \ldots, m` + + * :math:`\mathbf{x}_i^{(k+1)} = \mathrm{prox}_{\tau f_i} \left(\bar{\mathbf{x}}^{(k)} - \mathbf{y}_i^{(k)}\right)` + + * :math:`\bar{\mathbf{x}}^{(k+1)} = \frac{1}{m} \sum_{i=1}^m \mathbf{x}_i^{(k)}` + + * for :math:`i = 1, \ldots, m` + + * :math:`\mathbf{y}_i^{(k+1)} = \mathbf{y}_i^{(k)} + \mathbf{x}_i^{(k+1)} - \bar{\mathbf{x}}^{(k+1)}` + + The current implementation returns :math:`\bar{\mathbf{x}}`. + + References + ---------- + .. [1] Boyd, S., Parikh, N., Chu, E., Peleato, B., Eckstein, J., 2011. + Distributed Optimization and Statistical Learning via the Alternating + Direction Method of Multipliers. Foundations and Trends in Machine Learning, + Vol. 3, No. 1, pp 1-122. Section 7.1. https://doi.org/10.1561/2200000016 + https://stanford.edu/~boyd/papers/pdf/admm_distr_stats.pdf + .. [2] Parikh, N., Boyd, S., 2014. Proximal Algorithms. Foundations and + Trends in Optimization, Vol. 1, No. 3, pp 127-239. + Section 5.2.1. https://doi.org/10.1561/2400000003 + https://web.stanford.edu/~boyd/papers/pdf/prox_algs.pdf + + """ + if show: + tstart = time.time() + print( + "Consensus ADMM\n" + "---------------------------------------------------------" + ) + for i, prox_op in enumerate(prox_ops): + print(f"Proximal operator (f{i}): {type(prox_op)}") + print(f"tau = {tau:10e}\tniter = {niter:d}\n") + head = " Itn x[0] J=sum_i f_i" + print(head) + + ncp = get_array_module(x0) + + m = len(prox_ops) + x_bar = x0.copy() + x_bar_old = x0.copy() + y = ncp.zeros_like(x0) + + for iiter in range(niter): + + x = ncp.stack([prox_ops[i].prox(x_bar - y[i], tau) for i in range(m)]) + x_bar = ncp.mean(x, axis=0) + y = y + x - x_bar + + if callback is not None: + callback(x_bar) + + if show: + if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: + pf = ncp.sum([prox_ops[i](x_bar) for i in range(m)]) + print( + f"{iiter + 1:6d} {ncp.real(to_numpy(x_bar[0])):12.5e} " + f"{pf:10.3e}" + ) + + if ncp.abs(x_bar - x_bar_old).max() < tol: + break + + x_bar_old = x_bar + + if show: + print(f"\nTotal time (s) = {time.time() - tstart:.2f}") + print("---------------------------------------------------------\n") + + return x_bar diff --git a/pytests/test_solver.py b/pytests/test_solver.py index a6a1f42..b961224 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -1,3 +1,5 @@ +from typing import Dict, Any + import numpy as np import pytest from numpy.testing import assert_array_almost_equal @@ -11,6 +13,8 @@ GeneralizedProximalGradient, LinearizedADMM, ProximalGradient, + PPXA, + ConsensusADMM, ) from pyproximal.proximal import L1, L2 @@ -224,3 +228,220 @@ def test_ADMM_DRS(par): assert_array_almost_equal(xadmm, xdrs_g, decimal=2) assert_array_almost_equal(xadmm, xdrs_f, decimal=2) + + +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: + """Check equivalency of PPXA and ADMM + when using a single regularization term + """ + np.random.seed(0) + + n, m = par["n"], par["m"] + + # Define sparse model + x = np.zeros(m) + x[2], x[4] = 1, 0.5 + + # Random mixing matrix + R = np.random.normal(0.0, 1.0, (n, m)) + Rop = MatrixMult(R) + + y = Rop @ x + + l2 = L2(Op=Rop, b=y, niter=50, warm=False) + l1 = L1(sigma=5e-1) + + # Step size + L = (Rop.H * Rop).eigs(1).real.item() + tau = 0.5 / L + + xadmm, _ = ADMM( + l2, l1, x0=np.zeros(m), tau=tau, niter=15000, show=True) + xppxa = PPXA( + [l2, l1], x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + + assert_array_almost_equal(xadmm, xppxa, decimal=2) + + +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_PPXA_with_GPG(par: Dict[str, Any]) -> None: + """Check equivalency of PPXA and GeneralizedProximalGradient + """ + np.random.seed(0) + + n, m = par["n"], par["m"] + + # Define sparse model + x = np.zeros(m) + x[2], x[4] = 1, 0.5 + + g = np.zeros_like(x) + g[1], g[2] = 1, 0.5 + + # Random mixing matrices + R1 = np.random.normal(0.0, 1.0, (n, m)) + Rop1 = MatrixMult(R1) + y1 = Rop1 @ x + + R2 = np.random.normal(0.0, 1.0, (n, m)) + Rop2 = MatrixMult(R2) + y2 = Rop2 @ x + + l2_1 = L2(Op=Rop1, b=y1, niter=50, warm=False) + l2_2 = L2(Op=Rop2, b=y2, niter=50, warm=False) + l1_1 = L1(sigma=5e-1) + l1_2 = L1(sigma=2.5e-1, g=g) + + # Step size + L = (Rop1.H * Rop1).eigs(1).real.item() + tau = 0.5 / L + + xgpg = GeneralizedProximalGradient( + [l2_1, l2_2], + [l1_1, l1_2], + x0=np.zeros(m), tau=tau, niter=150, show=True) + xppxa = PPXA( + [l2_1, l2_2, l1_1, l1_2], + x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + + assert_array_almost_equal(xgpg, xppxa, decimal=2) + + +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: + """Check equivalency of ConsensusADMM and ADMM + when two proximable functions + """ + np.random.seed(0) + + n, m = par["n"], par["m"] + + # Define sparse model + x = np.zeros(m) + x[2], x[4] = 1, 0.5 + + # Random mixing matrix + R = np.random.normal(0.0, 1.0, (n, m)) + Rop = MatrixMult(R) + + y = Rop @ x + + l2 = L2(Op=Rop, b=y, niter=50, warm=False) + l1 = L1(sigma=5e-1) + + # Step size + L = (Rop.H * Rop).eigs(1).real.item() + tau = 0.5 / L + + xadmm, _ = ADMM( + l2, l1, x0=np.zeros(m), tau=tau, niter=15000, show=True) + xcadmm = ConsensusADMM( + [l2, l1], x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + + assert_array_almost_equal(xadmm, xcadmm, decimal=2) + + +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: + """Check equivalency of ConsensusADMM and ADMM + when more than two proximable functions for lasso + """ + m = par["m"] + lmd = 1e-2 + niter = 15000 + n_l2_ops = 3 + + np.random.seed(0) + + # Define sparse model + x_true = np.zeros(m) + nnz = np.random.randint(3, m // 2) + support = np.random.choice(m, size=nnz, replace=False) + x_true[support] = np.random.normal(0.0, 1.0, size=len(support)) + + # Random mixing matrix + R_list, y_list = [], [] + for ni in np.random.randint(3, 10, size=n_l2_ops): + R = np.random.normal(0.0, 1.0, size=(ni, m)) + R_list.append(R) + y_list.append(R @ x_true) + + # 1/2||R1||_2^2, 1/2||R2||_2^2, 1/2||R3||_2^2 + l2_ops = [ + L2(Op=MatrixMult(Ri), b=yi, niter=50, warm=False) + for Ri, yi in zip(R_list, y_list) + ] + + # 1/2 || [R1; R2; R3] ||_2^2 + Rop_stack = MatrixMult(np.vstack(R_list)) + y_stack = np.concatenate(y_list) + l2_stack = L2(Op=Rop_stack, b=y_stack, niter=50, warm=False) + + # ||x||_1 + l1_op = L1(sigma=lmd) + + # Step size + L = (Rop_stack.H * Rop_stack).eigs(1).real.item() + tau = 0.5 / L + + # 1/2||R1||_2^2 + 1/2||R2||_2^2 + 1/2||R3||_2^2 + ||x||_1 + x_cons = ConsensusADMM( + prox_ops=[*l2_ops, l1_op], + x0=np.zeros(m), + tau=tau, + niter=niter, + tol=1e-7, + show=False, + ) + + # 1/2 || [R1; R2; R3] ||_2^2 + ||x||_1 + x_lasso, _ = ADMM( + l2_stack, l1_op, x0=np.zeros(m), tau=tau, niter=niter, show=False + ) + + assert_array_almost_equal(x_cons, x_lasso, decimal=2) + + +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_ConsensusADMM_with_GPG(par: Dict[str, Any]) -> None: + """Check equivalency of ConsensusADMM and GeneralizedProximalGradient + """ + np.random.seed(0) + + n, m = par["n"], par["m"] + + # Define sparse model + x = np.zeros(m) + x[2], x[4] = 1, 0.5 + + g = np.zeros_like(x) + g[1], g[2] = 1, 0.5 + + # Random mixing matrices + R1 = np.random.normal(0.0, 1.0, (n, m)) + Rop1 = MatrixMult(R1) + y1 = Rop1 @ x + + R2 = np.random.normal(0.0, 1.0, (n, m)) + Rop2 = MatrixMult(R2) + y2 = Rop2 @ x + + l2_1 = L2(Op=Rop1, b=y1, niter=50, warm=False) + l2_2 = L2(Op=Rop2, b=y2, niter=50, warm=False) + l1_1 = L1(sigma=5e-1) + l1_2 = L1(sigma=2.5e-1, g=g) + + # Step size + L = (Rop1.H * Rop1).eigs(1).real.item() + tau = 0.5 / L + + xgpg = GeneralizedProximalGradient( + [l2_1, l2_2], + [l1_1, l1_2], + x0=np.zeros(m), tau=tau, niter=150, show=True) + xppxa = ConsensusADMM( + [l2_1, l2_2, l1_1, l1_2], + x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + + assert_array_almost_equal(xgpg, xppxa, decimal=2) From 5b75d406cd7329720c37415468f58a64585d650d Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 27 Dec 2025 22:23:19 +0000 Subject: [PATCH 03/72] ci: fix OSX Azure pipeline --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dbd03fe..34f2232 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -46,7 +46,7 @@ jobs: displayName: 'Mac' pool: - vmImage: 'macOS-latest' + vmImage: 'macOS-15-arm64' variables: NUMBA_NUM_THREADS: 1 @@ -55,7 +55,7 @@ jobs: - task: UsePythonVersion@0 inputs: versionSpec: '3.11' - architecture: 'x64' + architecture: 'arm64' - script: | python -m pip install --upgrade pip setuptools wheel django From aaab2ec91d2247f4df331d503edfe1e30f9d0265 Mon Sep 17 00:00:00 2001 From: Toru Tamaki Date: Mon, 5 Jan 2026 13:57:27 +0900 Subject: [PATCH 04/72] PR of PPXA and ConsensusADMM refined primal.py - prox_ops --> proxfs - np --> numpy - fix description of niter - remove () in math superscripts - add some comments test_solver.py - change niter to fit each test with seeds 0 to 499 - change tau to random value - change the order of argments to assert_array_almost_equal (actual <--> desired) --- pyproximal/optimization/primal.py | 75 ++++++++++++++++------------ pytests/test_solver.py | 82 ++++++++++++++++++++++--------- 2 files changed, 102 insertions(+), 55 deletions(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index f92885c..f7a568a 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -1796,7 +1796,7 @@ def DouglasRachfordSplitting( def PPXA( # pylint: disable=invalid-name - prox_ops: List[ProxOperator], + proxfs: List[ProxOperator], x0: NDArray | List[NDArray], tau: float, eta: float = 1.0, @@ -1820,21 +1820,22 @@ def PPXA( # pylint: disable=invalid-name Parameters ---------- - prox_ops : :obj:`list` + proxfs : :obj:`list` A list of proximable functions :math:`f_1, \ldots, f_m`. - x0 : :obj:`np.ndarray` or :obj:`list` - Initial vector :math:`\mathbf{x}` of all :math:`f_i` if 1D :obj:`np.ndarray`, - or :math:`\mathbf{x}_{i}` of each :math:`f_i` for :math:`i=1,\ldots,m` - if :obj:`list` or 2D :obj:`np.ndarray`. + x0 : :obj:`numpy.ndarray` or :obj:`list` + Initial vector :math:`\mathbf{x}` for all :math:`f_i` if 1-dimensional array + is provided, or initial vectors :math:`\mathbf{x}_{i}` for each :math:`f_i` + for :math:`i=1,\ldots,m` if a :obj:`list` of 1-dimensional arrays or a 2-dimensional + array of size ``(m, d)`` is provided, where ``d`` is the dimension of :math:`\mathbf{x}_{i}`. tau : :obj:`float` Positive scalar weight eta : :obj:`float`, optional Relaxation parameter (must be between 0 and 2, 0 excluded). - weights : :obj:`np.ndarray` or :obj:`list` or :obj:`None`, optional + weights : :obj:`numpy.ndarray` or :obj:`list` or :obj:`None`, optional Weights :math:`\sum_{i=1}^m w_i = 1, \ 0 < w_i < 1`, Defaults to None, which means :math:`w_1 = \cdots = w_m = \frac{1}{m}.` niter : :obj:`int`, optional - The maximum number of iterations. + Number of iterations of iterative scheme. tol : :obj:`float`, optional Tolerance on change of the solution (used as stopping criterion). If ``tol=0``, run until ``niter`` is reached. @@ -1858,20 +1859,20 @@ def PPXA( # pylint: disable=invalid-name The Parallel Proximal Algorithm (PPXA) can be expressed by the following recursion [1]_, [2]_, [3]_, [4]_: - * :math:`\mathbf{y}_{i}^{(0)} = \mathbf{x}` or :math:`\mathbf{y}_{i}^{(0)} = \mathbf{x}_{i}` for :math:`i=1,\ldots,m` - * :math:`\mathbf{x}^{(0)} = \sum_{i=1}^m w_i \mathbf{y}_{i}^{(0)}` + * :math:`\mathbf{y}_{i}^{0} = \mathbf{x}` or :math:`\mathbf{y}_{i}^{0} = \mathbf{x}_{i}` for :math:`i=1,\ldots,m` + * :math:`\mathbf{x}^{0} = \sum_{i=1}^m w_i \mathbf{y}_{i}^{0}` * for :math:`k = 1, \ldots` * for :math:`i = 1, \ldots, m` - * :math:`\mathbf{p}_{i}^{(k)} = \prox_{\frac{\tau}{w_i} f_i} (\mathbf{y}_{i}^{(k)})` + * :math:`\mathbf{p}_{i}^{k} = \prox_{\frac{\tau}{w_i} f_i} (\mathbf{y}_{i}^{k})` - * :math:`\mathbf{p}^{(k)} = \sum_{i=1}^{m} w_i \mathbf{p}_{i}^{(k)}` + * :math:`\mathbf{p}^{k} = \sum_{i=1}^{m} w_i \mathbf{p}_{i}^{k}` * for :math:`i = 1, \ldots, m` - * :math:`\mathbf{y}_{i}^{(k+1)} = \mathbf{y}_{i}^{(k)} + \eta (2 \mathbf{p}^{(k)} - \mathbf{x}^{(k)} - \mathbf{p}_i^{(k)})` + * :math:`\mathbf{y}_{i}^{k+1} = \mathbf{y}_{i}^{k} + \eta (2 \mathbf{p}^{k} - \mathbf{x}^{k} - \mathbf{p}_i^{k})` - * :math:`\mathbf{x}^{(k+1)} = \mathbf{x}^{(k)} + \eta (\mathbf{p}^{(k)} - \mathbf{x}^{(k)})` + * :math:`\mathbf{x}^{k+1} = \mathbf{x}^{k} + \eta (\mathbf{p}^{k} - \mathbf{x}^{k})` where :math:`0 < \eta < 2` and :math:`\sum_{i=1}^m w_i = 1, \ 0 < w_i < 1`. @@ -1903,15 +1904,16 @@ def PPXA( # pylint: disable=invalid-name "Parallel Proximal Algorithm\n" "---------------------------------------------------------" ) - for i, prox_op in enumerate(prox_ops): - print(f"Proximal operator (f{i}): {type(prox_op)}") + for i, proxf in enumerate(proxfs): + print(f"Proximal operator (f{i}): {type(proxf)}") print(f"tau = {tau:10e}\tniter = {niter:d}\n") head = " Itn x[0] J=sum_i f_i" print(head) ncp = get_array_module(x0) - m = len(prox_ops) + # initialize model + m = len(proxfs) if weights is None: w = ncp.full(m, 1. / m) else: @@ -1925,24 +1927,28 @@ def PPXA( # pylint: disable=invalid-name x = ncp.mean(y, axis=0) x_old = x.copy() + # iterate for iiter in range(niter): - p = ncp.stack([prox_ops[i].prox(y[i], tau / w[i]) for i in range(m)]) + p = ncp.stack([proxfs[i].prox(y[i], tau / w[i]) for i in range(m)]) pn = ncp.sum(w[:, None] * p, axis=0) y = y + eta * (2 * pn - x - p) x = x + eta * (pn - x) + # run callback if callback is not None: callback(x) + # show iteration logger if show: if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: - pf = ncp.sum([prox_ops[i](x) for i in range(m)]) + pf = ncp.sum([proxfs[i](x) for i in range(m)]) print( f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} " f"{pf:10.3e}" ) + # break if tolerance condition is met if ncp.abs(x - x_old).max() < tol: break @@ -1956,7 +1962,7 @@ def PPXA( # pylint: disable=invalid-name def ConsensusADMM( # pylint: disable=invalid-name - prox_ops: List[ProxOperator], + proxfs: List[ProxOperator], x0: NDArray, tau: float, niter: int = 1000, @@ -1979,14 +1985,14 @@ def ConsensusADMM( # pylint: disable=invalid-name Parameters ---------- - prox_ops : :obj:`list` + proxfs : :obj:`list` A list of proximable functions :math:`f_1, \ldots, f_m`. - x0 : :obj:`np.ndarray` + x0 : :obj:`numpy.ndarray` Initial vector tau : :obj:`float` Positive scalar weight niter : :obj:`int`, optional - The maximum number of iterations. + Number of iterations of iterative scheme. tol : :obj:`float`, optional Tolerance on change of the solution (used as stopping criterion). If ``tol=0``, run until ``niter`` is reached. @@ -2011,18 +2017,18 @@ def ConsensusADMM( # pylint: disable=invalid-name The ADMM for the consensus problem can be expressed by the following recursion [1]_, [2]_: - * :math:`\bar{\mathbf{x}}^{(0)} = \mathbf{x}` + * :math:`\bar{\mathbf{x}}^{0} = \mathbf{x}` * for :math:`k = 1, \ldots` * for :math:`i = 1, \ldots, m` - * :math:`\mathbf{x}_i^{(k+1)} = \mathrm{prox}_{\tau f_i} \left(\bar{\mathbf{x}}^{(k)} - \mathbf{y}_i^{(k)}\right)` + * :math:`\mathbf{x}_i^{k+1} = \mathrm{prox}_{\tau f_i} \left(\bar{\mathbf{x}}^{k} - \mathbf{y}_i^{k}\right)` - * :math:`\bar{\mathbf{x}}^{(k+1)} = \frac{1}{m} \sum_{i=1}^m \mathbf{x}_i^{(k)}` + * :math:`\bar{\mathbf{x}}^{k+1} = \frac{1}{m} \sum_{i=1}^m \mathbf{x}_i^{k}` * for :math:`i = 1, \ldots, m` - * :math:`\mathbf{y}_i^{(k+1)} = \mathbf{y}_i^{(k)} + \mathbf{x}_i^{(k+1)} - \bar{\mathbf{x}}^{(k+1)}` + * :math:`\mathbf{y}_i^{k+1} = \mathbf{y}_i^{k} + \mathbf{x}_i^{k+1} - \bar{\mathbf{x}}^{k+1}` The current implementation returns :math:`\bar{\mathbf{x}}`. @@ -2045,36 +2051,41 @@ def ConsensusADMM( # pylint: disable=invalid-name "Consensus ADMM\n" "---------------------------------------------------------" ) - for i, prox_op in enumerate(prox_ops): - print(f"Proximal operator (f{i}): {type(prox_op)}") + for i, proxf in enumerate(proxfs): + print(f"Proximal operator (f{i}): {type(proxf)}") print(f"tau = {tau:10e}\tniter = {niter:d}\n") head = " Itn x[0] J=sum_i f_i" print(head) ncp = get_array_module(x0) - m = len(prox_ops) + # initialize model + m = len(proxfs) x_bar = x0.copy() x_bar_old = x0.copy() y = ncp.zeros_like(x0) + # iterate for iiter in range(niter): - x = ncp.stack([prox_ops[i].prox(x_bar - y[i], tau) for i in range(m)]) + x = ncp.stack([proxfs[i].prox(x_bar - y[i], tau) for i in range(m)]) x_bar = ncp.mean(x, axis=0) y = y + x - x_bar + # run callback if callback is not None: callback(x_bar) + # show iteration logger if show: if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: - pf = ncp.sum([prox_ops[i](x_bar) for i in range(m)]) + pf = ncp.sum([proxfs[i](x_bar) for i in range(m)]) print( f"{iiter + 1:6d} {ncp.real(to_numpy(x_bar[0])):12.5e} " f"{pf:10.3e}" ) + # break if tolerance condition is met if ncp.abs(x_bar - x_bar_old).max() < tol: break diff --git a/pytests/test_solver.py b/pytests/test_solver.py index b961224..2c532eb 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -257,11 +257,20 @@ def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: tau = 0.5 / L xadmm, _ = ADMM( - l2, l1, x0=np.zeros(m), tau=tau, niter=15000, show=True) + l2, l1, + x0=np.zeros(m), + tau=tau, + niter=2000, # niter=1500 makes this test fail for seeds 0 to 499 + show=True, + ) xppxa = PPXA( - [l2, l1], x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + [l2, l1], + x0=np.zeros(m), + tau=np.random.uniform(3 * tau, 5 * tau), + show=True, + ) - assert_array_almost_equal(xadmm, xppxa, decimal=2) + assert_array_almost_equal(xppxa, xadmm, decimal=2) @pytest.mark.parametrize("par", [(par1), (par2)]) @@ -300,12 +309,19 @@ def test_PPXA_with_GPG(par: Dict[str, Any]) -> None: xgpg = GeneralizedProximalGradient( [l2_1, l2_2], [l1_1, l1_2], - x0=np.zeros(m), tau=tau, niter=150, show=True) + x0=np.zeros(m), + tau=tau, + niter=200, # niter=150 makes this test fail for seeds 0 to 499 + show=True, + ) xppxa = PPXA( [l2_1, l2_2, l1_1, l1_2], - x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + x0=np.zeros(m), + tau=np.random.uniform(3 * tau, 5 * tau), + show=True + ) - assert_array_almost_equal(xgpg, xppxa, decimal=2) + assert_array_almost_equal(xppxa, xgpg, decimal=2) @pytest.mark.parametrize("par", [(par1), (par2)]) @@ -335,11 +351,20 @@ def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: tau = 0.5 / L xadmm, _ = ADMM( - l2, l1, x0=np.zeros(m), tau=tau, niter=15000, show=True) + l2, l1, + x0=np.zeros(m), + tau=tau, + niter=2000, # niter=1500 makes this test fail for seeds 0 to 499 + show=True + ) xcadmm = ConsensusADMM( - [l2, l1], x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + [l2, l1], + x0=np.random.normal(0.0, 1.0, m), # x0=np.zeros(m), + tau=np.random.uniform(3 * tau, 5 * tau), + show=True, + ) - assert_array_almost_equal(xadmm, xcadmm, decimal=2) + assert_array_almost_equal(xcadmm, xadmm, decimal=2) @pytest.mark.parametrize("par", [(par1), (par2)]) @@ -349,7 +374,6 @@ def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: """ m = par["m"] lmd = 1e-2 - niter = 15000 n_l2_ops = 3 np.random.seed(0) @@ -386,27 +410,32 @@ def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: tau = 0.5 / L # 1/2||R1||_2^2 + 1/2||R2||_2^2 + 1/2||R3||_2^2 + ||x||_1 - x_cons = ConsensusADMM( - prox_ops=[*l2_ops, l1_op], - x0=np.zeros(m), - tau=tau, - niter=niter, - tol=1e-7, - show=False, + xcadmm = ConsensusADMM( + [*l2_ops, l1_op], + x0=np.random.normal(0.0, 1.0, m), # x0=np.zeros(m), + tau=np.random.uniform(3 * tau, 5 * tau), + niter=20000, # niter=15000 makes this test fail for seeds 0 to 499 + show=True, ) # 1/2 || [R1; R2; R3] ||_2^2 + ||x||_1 - x_lasso, _ = ADMM( - l2_stack, l1_op, x0=np.zeros(m), tau=tau, niter=niter, show=False + xadmm, _ = ADMM( + l2_stack, + l1_op, + x0=np.zeros(m), + tau=tau, + niter=15000, # niter=10000 makes this test fail for seeds 0 to 499 + show=True, ) - assert_array_almost_equal(x_cons, x_lasso, decimal=2) + assert_array_almost_equal(xcadmm, xadmm, decimal=2) @pytest.mark.parametrize("par", [(par1), (par2)]) def test_ConsensusADMM_with_GPG(par: Dict[str, Any]) -> None: """Check equivalency of ConsensusADMM and GeneralizedProximalGradient """ + np.random.seed(0) n, m = par["n"], par["m"] @@ -439,9 +468,16 @@ def test_ConsensusADMM_with_GPG(par: Dict[str, Any]) -> None: xgpg = GeneralizedProximalGradient( [l2_1, l2_2], [l1_1, l1_2], - x0=np.zeros(m), tau=tau, niter=150, show=True) + x0=np.zeros(m), + tau=tau, + niter=200, # niter=150 makes this test fail for seeds 0 to 499 + show=True, + ) xppxa = ConsensusADMM( [l2_1, l2_2, l1_1, l1_2], - x0=np.zeros(m), tau=tau, niter=15000, show=True, tol=1e-7) + x0=np.zeros(m), + tau=np.random.uniform(3 * tau, 5 * tau), + show=True, + ) - assert_array_almost_equal(xgpg, xppxa, decimal=2) + assert_array_almost_equal(xppxa, xgpg, decimal=2) From c4b8db0ff58e98e2d897c1d833756e9c10b35a24 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Thu, 22 Jan 2026 23:33:34 +0000 Subject: [PATCH 05/72] doc: added deblending tutorial --- testdata/mobil.npy | Bin 0 -> 240128 bytes tutorials/deblending.py | 186 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100755 testdata/mobil.npy create mode 100644 tutorials/deblending.py diff --git a/testdata/mobil.npy b/testdata/mobil.npy new file mode 100755 index 0000000000000000000000000000000000000000..6d09cb51c6bb0caefe3cd55324b9c876e05bbdbf GIT binary patch literal 240128 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%0DL&qp*e=RyDOSy$;Y%d<9@^`zE6d2+GU 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 z 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%0At|Pik!e_7o=i4W1;n~E1SdfpMlFF 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 z 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 QXHQ4=U-}nblcDne0JCE0RR910 literal 0 HcmV?d00001 diff --git a/tutorials/deblending.py b/tutorials/deblending.py new file mode 100644 index 0000000..59151d8 --- /dev/null +++ b/tutorials/deblending.py @@ -0,0 +1,186 @@ +r""" +Deblending +========== +This is the continuation of the ``deblending`` tutorial in PyLops, where a novel approach +to deblending using a hard-data constraint is implementent [1]_. + +In other words, we aim to solve the following problem: + +.. math:: + J = i_{\mathbf{d}^b-\boldsymbol\Phi \mathbf{d}} + ||\mathbf{S}^H \mathbf{d}||_0 + +where :math:`\mathbf{d} = [\mathbf{d}_1^T, \mathbf{d}_2^T,\ldots, +\mathbf{d}_N^T]^T` is a stack of :math:`N` individual shot gathers, +:math:`\boldsymbol\Phi=[\boldsymbol\Phi_1, \boldsymbol\Phi_2,\ldots, +\boldsymbol\Phi_N]` is the blending operator, :math:`\mathbf{d}^b` is the +so-called supergather than contains all shots superimposed to each other, +:math:`\mathbf{S}` is a patch-FK transform, and :math:`i_{\mathbf{d}^b-\boldsymbol\Phi \mathbf{d}}` +is the indicator function of the :py:class:`pyproximal.AffineSet`. + +References +---------- +1. M. Ravasi, "Seismic deblending with a hard data constraint", IMAGE, 2024. + +""" + +import matplotlib.pyplot as plt +import numpy as np +import pylops + +import pyproximal + +np.random.seed(10) +plt.close("all") + +############################################################################### +# Let's load and display a small portion of the MobilAVO dataset composed +# of 60 shots and a single receiver. This data is unblended. + +data = np.load("../testdata/mobil.npy") +ns, nt = data.shape + +dt = 0.004 +t = np.arange(nt) * dt + +fig, ax = plt.subplots(1, 1, figsize=(12, 8)) +ax.imshow( + data.T, + cmap="gray", + vmin=-50, + vmax=50, + extent=(0, ns, t[-1], 0), + interpolation="none", +) +ax.set_title("CRG") +ax.set_xlabel("#Src") +ax.set_ylabel("t [s]") +ax.axis("tight") +plt.tight_layout() + +############################################################################### +# We are now ready to define the blending operator, blend our data, and apply +# the adjoint of the blending operator to it. This is usually referred as +# pseudo-deblending: as we will see brings back each source to its own nominal +# firing time, but since sources partially overlap in time, it will also generate +# some burst like noise in the data. Deblending can hopefully fix this. + +overlap = 0.5 +ignition_times = 2.0 * np.random.rand(ns) - 1.0 +ignition_times = np.arange(0, overlap * nt * ns, overlap * nt) * dt + ignition_times +ignition_times[0] = 0.0 +Bop = pylops.waveeqprocessing.BlendingContinuous( + nt, 1, ns, dt, ignition_times, dtype="complex128" +) + +data_blended = Bop * data[:, np.newaxis] +data_pseudo = Bop.H * data_blended +data_pseudo = data_pseudo.reshape(ns, nt) + +fig, ax = plt.subplots(1, 1, figsize=(12, 8)) +ax.imshow( + data_pseudo.T.real, + cmap="gray", + vmin=-50, + vmax=50, + extent=(0, ns, t[-1], 0), + interpolation="none", +) +ax.set_title("Pseudo-deblended CRG") +ax.set_xlabel("#Src") +ax.set_ylabel("t [s]") +ax.axis("tight") +plt.tight_layout() + +############################################################################### +# We are finally ready to solve our deblending inverse problem + +# Patched FK +dimsd = data.shape +nwin = (20, 80) +nover = (10, 40) +nop = (128, 128) +nop1 = (128, 65) +nwins = (5, 24) +dims = (nwins[0] * nop1[0], nwins[1] * nop1[1]) + +Fop = pylops.signalprocessing.FFT2D(nwin, nffts=nop, real=True) +Sop = pylops.signalprocessing.Patch2D( + Fop.H, dims, dimsd, nwin, nover, nop1, tapertype="cosinesqrt" +) +S1op = pylops.signalprocessing.Patch2D( + Fop.H, dims, dimsd, nwin, nover, nop1, tapertype=None +) + +# Define max eigenvalue (we hard-code it here for simplicity) +maxeig = 3 + +# Define max value in the patch-FK domain for normalization +# and iteration-dependent thresholding +data_fkpatches = (S1op.H @ data_pseudo.ravel()).reshape(Sop.dims) +max_fkpatches = np.abs(data_fkpatches).max() + + +def sigma(iiter): + return (max_fkpatches * 0.6) * (0.9**iiter) + + +# Deblend +niter = 60 + +tau = 0.99 / maxeig +laff = pyproximal.proximal.AffineSet(Bop, data_blended.ravel(), niter=5) +lort = pyproximal.proximal.Orthogonal(pyproximal.proximal.L0(sigma=sigma), Sop.H) + +data_inv = pyproximal.optimization.primal.HQS( + laff, lort, x0=np.zeros(Bop.shape[1]), tau=tau, gfirst=False, niter=niter, show=True +)[0] +data_inv = data_inv.reshape(ns, nt) +snr_inv = pylops.utils.metrics.snr(data, data_inv) + +fig, axs = plt.subplots(1, 4, sharey=False, figsize=(12, 8)) +axs[0].imshow( + data.T.real, + cmap="gray", + extent=(0, ns, t[-1], 0), + vmin=-50, + vmax=50, + interpolation="none", +) +axs[0].set_title("CRG") +axs[0].set_xlabel("#Src") +axs[0].set_ylabel("t [s]") +axs[0].axis("tight") +axs[1].imshow( + data_pseudo.T.real, + cmap="gray", + extent=(0, ns, t[-1], 0), + vmin=-50, + vmax=50, + interpolation="none", +) +axs[1].set_title("Pseudo-deblended CRG") +axs[1].set_xlabel("#Src") +axs[1].axis("tight") +axs[2].imshow( + data_inv.T.real, + cmap="gray", + extent=(0, ns, t[-1], 0), + vmin=-50, + vmax=50, + interpolation="none", +) +axs[2].set_xlabel("#Src") +axs[2].set_title(f"Deblended CRG (SNR: {snr_inv:.2f} dB)") +axs[2].axis("tight") +axs[3].imshow( + data.T.real - data_inv.T.real, + cmap="gray", + extent=(0, ns, t[-1], 0), + vmin=-50, + vmax=50, + interpolation="none", +) +axs[3].set_xlabel("#Src") +axs[3].set_title("Blending error") +axs[3].axis("tight") +plt.tight_layout() From ce81e456ab27d8687e84786ce1650513caa359fd Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 31 Jan 2026 20:22:27 +0000 Subject: [PATCH 06/72] build: added support for uv --- .github/workflows/build.yaml | 10 +- .github/workflows/build_uv.yaml | 33 + Makefile | 31 +- environment-dev-arm.yml | 4 +- environment-dev.yml | 4 +- pyproject.toml | 29 +- requirements-dev.txt | 4 +- requirements-doc.txt | 4 +- uv.lock | 3140 +++++++++++++++++++++++++++++++ 9 files changed, 3251 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/build_uv.yaml create mode 100644 uv.lock diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2c13a40..abc3f9f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,6 +1,12 @@ -name: PyProx +name: PyProximal Testing -on: [push, pull_request] +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - main + - dev jobs: build: diff --git a/.github/workflows/build_uv.yaml b/.github/workflows/build_uv.yaml new file mode 100644 index 0000000..0b9d16e --- /dev/null +++ b/.github/workflows/build_uv.yaml @@ -0,0 +1,33 @@ +name: PyProximal Testing with UV + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - main + - dev + +jobs: + build: + strategy: + matrix: + platform: [ ubuntu-latest, macos-latest ] + python-version: ["3.10", "3.11", "3.12"] + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Install uv with Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and pyproximal + run: | + run: uv sync --locked --all-extras --all-groups + - name: Test with pytest + run: uv run pytest --color=yes pytests/ diff --git a/Makefile b/Makefile index 3ba8000..ad58435 100755 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ PIP := $(shell command -v pip3 2> /dev/null || command which pip 2> /dev/null) PYTHON := $(shell command -v python3 2> /dev/null || command which python 2> /dev/null) +UV := $(shell command -v uv 2> /dev/null || command which uv 2> /dev/null) -.PHONY: install dev-install install_conda dev-install_conda tests doc docupdate servedoc lint typeannot +.PHONY: install dev-install install_conda dev-install_conda tests tests_uv doc doc_uv docupdate docupdate_uv servedoc lint lint_uv typeannot typeannot_uv pipcheck: ifndef PIP @@ -15,6 +16,12 @@ ifndef PYTHON endif @echo Using python: $(PYTHON) +uvcheck: +ifndef UV + $(error "Ensure uv is in your PATH") +endif + @echo Using uv: $(UV) + install: make pipcheck $(PIP) install -r requirements.txt && $(PIP) install . @@ -36,19 +43,41 @@ tests: make pythoncheck pytest +tests_uv: + make uvcheck + $(UV) run pytest + doc: cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\ rm -rf source/tutorials && rm -rf source/examples &&\ rm -rf build && make html && cd .. +doc_uv: + make uvcheck + cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\ + rm -rf source/tutorials && rm -rf source/examples &&\ + rm -rf build && $(UV) run make html && cd .. + docupdate: cd docs && make html && cd .. +docupdate_uv: + make uvcheck + cd docs && $(UV) run make html && cd .. + servedoc: $(PYTHON) -m http.server --directory docs/build/html/ lint: flake8 docs/source examples/ pyproximal/ pytests/ tutorials/ +lint_uv: + make uvcheck + $(UV) run flake8 docs/source examples/ pyproximal/ pytests/ tutorials/ + typeannot: mypy pyproximal/ + +typeannot_uv: + make uvcheck + $(UV) run mypy pyproximal/ diff --git a/environment-dev-arm.yml b/environment-dev-arm.yml index d86f2b0..d99d889 100644 --- a/environment-dev-arm.yml +++ b/environment-dev-arm.yml @@ -26,8 +26,10 @@ dependencies: - pytest-runner - setuptools_scm - pooch - - pydata-sphinx-theme + - shibuya + - sphinx-design - sphinx-gallery + - sphinx-iconify - nbsphinx - sphinxemoji - image diff --git a/environment-dev.yml b/environment-dev.yml index 9718bec..189f04f 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -27,8 +27,10 @@ dependencies: - pytest-runner - setuptools_scm - pooch - - pydata-sphinx-theme + - shibuya + - sphinx-design - sphinx-gallery + - sphinx-iconify - nbsphinx - sphinxemoji - image diff --git a/pyproject.toml b/pyproject.toml index e242ead..11bc6e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: Mathematics", ] +requires-python = ">=3.10" dependencies = [ "numpy >= 1.21.0", "scipy >= 1.11.0", @@ -43,6 +44,33 @@ advanced = [ "numba", ] +[dependency-groups] +dev = [ + "pytest", + "isort", + "flake8", + "pre-commit", + "autopep8", + "black", + "mypy", +] +doc = [ + "matplotlib", + "scikit-image", + "bm3d<4.2.4", + "bm4d<4.2.4", + "pooch", + "sphinx", + "sphinx-design", + "sphinx-gallery", + "sphinx-iconify", + "shibuya", + "sphinxemoji", + "numpydoc", + "nbsphinx", + "image", +] + [tool.setuptools.packages.find] exclude = ["pytests"] @@ -59,7 +87,6 @@ warn_unreachable = true allow_redefinition = true disallow_untyped_defs = true disallow_incomplete_defs = true -plugins = ["numpy.typing.mypy_plugin"] [[tool.mypy.overrides]] module = ["cupy.*", "numpy.*", "numba.*", "pylops.*", "scipy.*", "scooby.*", "pyproximal.*"] diff --git a/requirements-dev.txt b/requirements-dev.txt index b0d5703..4f5d744 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,8 +14,10 @@ setuptools_scm docutils<0.18 Sphinx pooch -pydata-sphinx-theme +shibuya +sphinx-design sphinx-gallery +sphinx-iconify sphinxemoji numpydoc nbsphinx diff --git a/requirements-doc.txt b/requirements-doc.txt index b0d5703..4f5d744 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -14,8 +14,10 @@ setuptools_scm docutils<0.18 Sphinx pooch -pydata-sphinx-theme +shibuya +sphinx-design sphinx-gallery +sphinx-iconify sphinxemoji numpydoc nbsphinx diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..508f2e3 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3140 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "asgiref" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "autopep8" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycodestyle" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/d8/30873d2b7b57dee9263e53d142da044c4600a46f2d28374b3e38b023df16/autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758", size = 92210, upload-time = "2025-01-14T14:46:18.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128", size = 45807, upload-time = "2025-01-14T14:46:15.466Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "black" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/1b/523329e713f965ad0ea2b7a047eeb003007792a0353622ac7a8cb2ee6fef/black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168", size = 1849661, upload-time = "2026-01-18T04:59:12.425Z" }, + { url = "https://files.pythonhosted.org/packages/14/82/94c0640f7285fa71c2f32879f23e609dd2aa39ba2641f395487f24a578e7/black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d", size = 1689065, upload-time = "2026-01-18T04:59:13.993Z" }, + { url = "https://files.pythonhosted.org/packages/f0/78/474373cbd798f9291ed8f7107056e343fd39fef42de4a51c7fd0d360840c/black-26.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0", size = 1751502, upload-time = "2026-01-18T04:59:15.971Z" }, + { url = "https://files.pythonhosted.org/packages/29/89/59d0e350123f97bc32c27c4d79563432d7f3530dca2bff64d855c178af8b/black-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24", size = 1400102, upload-time = "2026-01-18T04:59:17.8Z" }, + { url = "https://files.pythonhosted.org/packages/e1/bc/5d866c7ae1c9d67d308f83af5462ca7046760158bbf142502bad8f22b3a1/black-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89", size = 1207038, upload-time = "2026-01-18T04:59:19.543Z" }, + { url = "https://files.pythonhosted.org/packages/30/83/f05f22ff13756e1a8ce7891db517dbc06200796a16326258268f4658a745/black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5", size = 1831956, upload-time = "2026-01-18T04:59:21.38Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/b2c570550e39bedc157715e43927360312d6dd677eed2cc149a802577491/black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68", size = 1672499, upload-time = "2026-01-18T04:59:23.257Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/990d6a94dc9e169f61374b1c3d4f4dd3037e93c2cc12b6f3b12bc663aa7b/black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14", size = 1735431, upload-time = "2026-01-18T04:59:24.729Z" }, + { url = "https://files.pythonhosted.org/packages/36/1c/cbd7bae7dd3cb315dfe6eeca802bb56662cc92b89af272e014d98c1f2286/black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c", size = 1400468, upload-time = "2026-01-18T04:59:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/59/b1/9fe6132bb2d0d1f7094613320b56297a108ae19ecf3041d9678aec381b37/black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4", size = 1207332, upload-time = "2026-01-18T04:59:28.711Z" }, + { url = "https://files.pythonhosted.org/packages/f5/13/710298938a61f0f54cdb4d1c0baeb672c01ff0358712eddaf29f76d32a0b/black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f", size = 1878189, upload-time = "2026-01-18T04:59:30.682Z" }, + { url = "https://files.pythonhosted.org/packages/79/a6/5179beaa57e5dbd2ec9f1c64016214057b4265647c62125aa6aeffb05392/black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6", size = 1700178, upload-time = "2026-01-18T04:59:32.387Z" }, + { url = "https://files.pythonhosted.org/packages/8c/04/c96f79d7b93e8f09d9298b333ca0d31cd9b2ee6c46c274fd0f531de9dc61/black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a", size = 1777029, upload-time = "2026-01-18T04:59:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/49/f9/71c161c4c7aa18bdda3776b66ac2dc07aed62053c7c0ff8bbda8c2624fe2/black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791", size = 1406466, upload-time = "2026-01-18T04:59:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8b/a7b0f974e473b159d0ac1b6bcefffeb6bec465898a516ee5cc989503cbc7/black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954", size = 1216393, upload-time = "2026-01-18T04:59:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/79/04/fa2f4784f7237279332aa735cdfd5ae2e7730db0072fb2041dadda9ae551/black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304", size = 1877781, upload-time = "2026-01-18T04:59:39.054Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ad/5a131b01acc0e5336740a039628c0ab69d60cf09a2c87a4ec49f5826acda/black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9", size = 1699670, upload-time = "2026-01-18T04:59:41.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/7c/b05f22964316a52ab6b4265bcd52c0ad2c30d7ca6bd3d0637e438fc32d6e/black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b", size = 1775212, upload-time = "2026-01-18T04:59:42.545Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/e8d1526bea0446e040193185353920a9506eab60a7d8beb062029129c7d2/black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b", size = 1409953, upload-time = "2026-01-18T04:59:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5a/d62ebf4d8f5e3a1daa54adaab94c107b57be1b1a2f115a0249b41931e188/black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca", size = 1217707, upload-time = "2026-01-18T04:59:45.719Z" }, + { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "bm3d" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bm4d" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/0f/5387ec8e07b3345e9b8e23cba2a28e27a6b7bccb3e74e2e63dd23a8ca8b5/bm3d-4.0.1.tar.gz", hash = "sha256:92ad34a0923facd2d5211700093c7fff196133f28cf646244b0598a7daf90dd3", size = 1068761, upload-time = "2022-12-20T02:21:31.328Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/ff/ab6cb80798cde8cc14f1970f0a244494d0bdcb0d5371c61d030a7c55a7c5/bm3d-4.0.1-py3-none-any.whl", hash = "sha256:81e430d4a7aa5f30e51f8c61eab55265e3e074a4f5cea67ab0238eb4bf15cc7f", size = 9979, upload-time = "2022-12-20T02:21:17.289Z" }, +] + +[[package]] +name = "bm4d" +version = "4.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pywavelets", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pywavelets", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/c6/6b46e32625b9bf36bfcd4a1b7fd6ca6ecaff7d32034273b15e056f43d3ee/bm4d-4.2.3.tar.gz", hash = "sha256:e4cd0865f304f584118b9e8af880f0f2212ae9c5aa5192cf5d96c40007659de6", size = 4095146, upload-time = "2022-12-20T02:21:27.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/ab/59a1f5ea27bfb0f9ba306cd109a0f3b763e696530cc00a1ce52a1704de21/bm4d-4.2.3-py3-none-any.whl", hash = "sha256:752c3a5952624d760e5251ba38dd4e7e46b6e450deb7fc3bd05544128ac6c27a", size = 1068070, upload-time = "2022-12-20T02:21:15.4Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "django" +version = "5.2.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "asgiref", marker = "python_full_version < '3.12'" }, + { name = "sqlparse", marker = "python_full_version < '3.12'" }, + { name = "tzdata", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/e5/2671df24bf0ded831768ef79532e5a7922485411a5696f6d979568591a37/django-5.2.10.tar.gz", hash = "sha256:74df100784c288c50a2b5cad59631d71214f40f72051d5af3fdf220c20bdbbbe", size = 10880754, upload-time = "2026-01-06T18:55:26.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/de/f1a7cd896daec85832136ab509d9b2a6daed4939dbe26313af3e95fc5f5e/django-5.2.10-py3-none-any.whl", hash = "sha256:cf85067a64250c95d5f9067b056c5eaa80591929f7e16fbcd997746e40d6c45c", size = 8290820, upload-time = "2026-01-06T18:55:20.009Z" }, +] + +[[package]] +name = "django" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "asgiref", marker = "python_full_version >= '3.12'" }, + { name = "sqlparse", marker = "python_full_version >= '3.12'" }, + { name = "tzdata", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/9b/016f7e55e855ee738a352b05139d4f8b278d0b451bd01ebef07456ef3b0e/django-6.0.1.tar.gz", hash = "sha256:ed76a7af4da21551573b3d9dfc1f53e20dd2e6c7d70a3adc93eedb6338130a5f", size = 11069565, upload-time = "2026-01-06T18:55:53.069Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/b5/814ed98bd21235c116fd3436a7ed44d47560329a6d694ec8aac2982dbb93/django-6.0.1-py3-none-any.whl", hash = "sha256:a92a4ff14f664a896f9849009cb8afaca7abe0d6fc53325f3d1895a15253433d", size = 8338791, upload-time = "2026-01-06T18:55:46.175Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, +] + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" }, + { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, + { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, + { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, + { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + +[[package]] +name = "identify" +version = "2.6.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "image" +version = "1.5.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/be/961693ed384aa91bcc07525c90e3a34bc06c75f131655dfe21310234c933/image-1.5.33.tar.gz", hash = "sha256:baa2e09178277daa50f22fd6d1d51ec78f19c12688921cb9ab5808743f097126", size = 15975, upload-time = "2020-10-27T09:58:36.538Z" } + +[[package]] +name = "imageio" +version = "2.37.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isort" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, + { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, + { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, + { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, + { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, + { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, + { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, + { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, + { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, + { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, +] + +[[package]] +name = "librt" +version = "0.7.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/13/57b06758a13550c5f09563893b004f98e9537ee6ec67b7df85c3571c8832/librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d", size = 56521, upload-time = "2026-01-14T12:54:40.066Z" }, + { url = "https://files.pythonhosted.org/packages/c2/24/bbea34d1452a10612fb45ac8356f95351ba40c2517e429602160a49d1fd0/librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b", size = 58456, upload-time = "2026-01-14T12:54:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/04/72/a168808f92253ec3a810beb1eceebc465701197dbc7e865a1c9ceb3c22c7/librt-0.7.8-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d", size = 164392, upload-time = "2026-01-14T12:54:42.843Z" }, + { url = "https://files.pythonhosted.org/packages/14/5c/4c0d406f1b02735c2e7af8ff1ff03a6577b1369b91aa934a9fa2cc42c7ce/librt-0.7.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d", size = 172959, upload-time = "2026-01-14T12:54:44.602Z" }, + { url = "https://files.pythonhosted.org/packages/82/5f/3e85351c523f73ad8d938989e9a58c7f59fb9c17f761b9981b43f0025ce7/librt-0.7.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c", size = 186717, upload-time = "2026-01-14T12:54:45.986Z" }, + { url = "https://files.pythonhosted.org/packages/08/f8/18bfe092e402d00fe00d33aa1e01dda1bd583ca100b393b4373847eade6d/librt-0.7.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c", size = 184585, upload-time = "2026-01-14T12:54:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/4e/fc/f43972ff56fd790a9fa55028a52ccea1875100edbb856b705bd393b601e3/librt-0.7.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d", size = 180497, upload-time = "2026-01-14T12:54:48.946Z" }, + { url = "https://files.pythonhosted.org/packages/e1/3a/25e36030315a410d3ad0b7d0f19f5f188e88d1613d7d3fd8150523ea1093/librt-0.7.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0", size = 200052, upload-time = "2026-01-14T12:54:50.382Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b8/f3a5a1931ae2a6ad92bf6893b9ef44325b88641d58723529e2c2935e8abe/librt-0.7.8-cp310-cp310-win32.whl", hash = "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85", size = 43477, upload-time = "2026-01-14T12:54:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/fe/91/c4202779366bc19f871b4ad25db10fcfa1e313c7893feb942f32668e8597/librt-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c", size = 49806, upload-time = "2026-01-14T12:54:53.149Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a3/87ea9c1049f2c781177496ebee29430e4631f439b8553a4969c88747d5d8/librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f", size = 56507, upload-time = "2026-01-14T12:54:54.156Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4a/23bcef149f37f771ad30203d561fcfd45b02bc54947b91f7a9ac34815747/librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac", size = 58455, upload-time = "2026-01-14T12:54:55.978Z" }, + { url = "https://files.pythonhosted.org/packages/22/6e/46eb9b85c1b9761e0f42b6e6311e1cc544843ac897457062b9d5d0b21df4/librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c", size = 164956, upload-time = "2026-01-14T12:54:57.311Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3f/aa7c7f6829fb83989feb7ba9aa11c662b34b4bd4bd5b262f2876ba3db58d/librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8", size = 174364, upload-time = "2026-01-14T12:54:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2d/d57d154b40b11f2cb851c4df0d4c4456bacd9b1ccc4ecb593ddec56c1a8b/librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff", size = 188034, upload-time = "2026-01-14T12:55:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/59/f9/36c4dad00925c16cd69d744b87f7001792691857d3b79187e7a673e812fb/librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3", size = 186295, upload-time = "2026-01-14T12:55:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/23/9b/8a9889d3df5efb67695a67785028ccd58e661c3018237b73ad081691d0cb/librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75", size = 181470, upload-time = "2026-01-14T12:55:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/43/64/54d6ef11afca01fef8af78c230726a9394759f2addfbf7afc5e3cc032a45/librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873", size = 201713, upload-time = "2026-01-14T12:55:03.919Z" }, + { url = "https://files.pythonhosted.org/packages/2d/29/73e7ed2991330b28919387656f54109139b49e19cd72902f466bd44415fd/librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7", size = 43803, upload-time = "2026-01-14T12:55:04.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/de/66766ff48ed02b4d78deea30392ae200bcbd99ae61ba2418b49fd50a4831/librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c", size = 50080, upload-time = "2026-01-14T12:55:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e3/33450438ff3a8c581d4ed7f798a70b07c3206d298cf0b87d3806e72e3ed8/librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232", size = 43383, upload-time = "2026-01-14T12:55:07.49Z" }, + { url = "https://files.pythonhosted.org/packages/56/04/79d8fcb43cae376c7adbab7b2b9f65e48432c9eced62ac96703bcc16e09b/librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63", size = 57472, upload-time = "2026-01-14T12:55:08.528Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ba/60b96e93043d3d659da91752689023a73981336446ae82078cddf706249e/librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93", size = 58986, upload-time = "2026-01-14T12:55:09.466Z" }, + { url = "https://files.pythonhosted.org/packages/7c/26/5215e4cdcc26e7be7eee21955a7e13cbf1f6d7d7311461a6014544596fac/librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592", size = 168422, upload-time = "2026-01-14T12:55:10.499Z" }, + { url = "https://files.pythonhosted.org/packages/0f/84/e8d1bc86fa0159bfc24f3d798d92cafd3897e84c7fea7fe61b3220915d76/librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850", size = 177478, upload-time = "2026-01-14T12:55:11.577Z" }, + { url = "https://files.pythonhosted.org/packages/57/11/d0268c4b94717a18aa91df1100e767b010f87b7ae444dafaa5a2d80f33a6/librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62", size = 192439, upload-time = "2026-01-14T12:55:12.7Z" }, + { url = "https://files.pythonhosted.org/packages/8d/56/1e8e833b95fe684f80f8894ae4d8b7d36acc9203e60478fcae599120a975/librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b", size = 191483, upload-time = "2026-01-14T12:55:13.838Z" }, + { url = "https://files.pythonhosted.org/packages/17/48/f11cf28a2cb6c31f282009e2208312aa84a5ee2732859f7856ee306176d5/librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714", size = 185376, upload-time = "2026-01-14T12:55:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6a/d7c116c6da561b9155b184354a60a3d5cdbf08fc7f3678d09c95679d13d9/librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449", size = 206234, upload-time = "2026-01-14T12:55:16.571Z" }, + { url = "https://files.pythonhosted.org/packages/61/de/1975200bb0285fc921c5981d9978ce6ce11ae6d797df815add94a5a848a3/librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac", size = 44057, upload-time = "2026-01-14T12:55:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cd/724f2d0b3461426730d4877754b65d39f06a41ac9d0a92d5c6840f72b9ae/librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708", size = 50293, upload-time = "2026-01-14T12:55:19.179Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cf/7e899acd9ee5727ad8160fdcc9994954e79fab371c66535c60e13b968ffc/librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0", size = 43574, upload-time = "2026-01-14T12:55:20.185Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fe/b1f9de2829cf7fc7649c1dcd202cfd873837c5cc2fc9e526b0e7f716c3d2/librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", size = 57500, upload-time = "2026-01-14T12:55:21.219Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d4/4a60fbe2e53b825f5d9a77325071d61cd8af8506255067bf0c8527530745/librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", size = 59019, upload-time = "2026-01-14T12:55:22.256Z" }, + { url = "https://files.pythonhosted.org/packages/6a/37/61ff80341ba5159afa524445f2d984c30e2821f31f7c73cf166dcafa5564/librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", size = 169015, upload-time = "2026-01-14T12:55:23.24Z" }, + { url = "https://files.pythonhosted.org/packages/1c/86/13d4f2d6a93f181ebf2fc953868826653ede494559da8268023fe567fca3/librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6", size = 178161, upload-time = "2026-01-14T12:55:24.826Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/e24ef01305954fc4d771f1f09f3dd682f9eb610e1bec188ffb719374d26e/librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d", size = 193015, upload-time = "2026-01-14T12:55:26.04Z" }, + { url = "https://files.pythonhosted.org/packages/88/a0/92b6bd060e720d7a31ed474d046a69bd55334ec05e9c446d228c4b806ae3/librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e", size = 192038, upload-time = "2026-01-14T12:55:27.208Z" }, + { url = "https://files.pythonhosted.org/packages/06/bb/6f4c650253704279c3a214dad188101d1b5ea23be0606628bc6739456624/librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca", size = 186006, upload-time = "2026-01-14T12:55:28.594Z" }, + { url = "https://files.pythonhosted.org/packages/dc/00/1c409618248d43240cadf45f3efb866837fa77e9a12a71481912135eb481/librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93", size = 206888, upload-time = "2026-01-14T12:55:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/d9/83/b2cfe8e76ff5c1c77f8a53da3d5de62d04b5ebf7cf913e37f8bca43b5d07/librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951", size = 44126, upload-time = "2026-01-14T12:55:31.44Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0b/c59d45de56a51bd2d3a401fc63449c0ac163e4ef7f523ea8b0c0dee86ec5/librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34", size = 50262, upload-time = "2026-01-14T12:55:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b9/973455cec0a1ec592395250c474164c4a58ebf3e0651ee920fef1a2623f1/librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09", size = 43600, upload-time = "2026-01-14T12:55:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/1a/73/fa8814c6ce2d49c3827829cadaa1589b0bf4391660bd4510899393a23ebc/librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", size = 57049, upload-time = "2026-01-14T12:55:35.056Z" }, + { url = "https://files.pythonhosted.org/packages/53/fe/f6c70956da23ea235fd2e3cc16f4f0b4ebdfd72252b02d1164dd58b4e6c3/librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", size = 58689, upload-time = "2026-01-14T12:55:36.078Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4d/7a2481444ac5fba63050d9abe823e6bc16896f575bfc9c1e5068d516cdce/librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", size = 166808, upload-time = "2026-01-14T12:55:37.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/3c/10901d9e18639f8953f57c8986796cfbf4c1c514844a41c9197cf87cb707/librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", size = 175614, upload-time = "2026-01-14T12:55:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/01/5cbdde0951a5090a80e5ba44e6357d375048123c572a23eecfb9326993a7/librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", size = 189955, upload-time = "2026-01-14T12:55:39.939Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b4/e80528d2f4b7eaf1d437fcbd6fc6ba4cbeb3e2a0cb9ed5a79f47c7318706/librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", size = 189370, upload-time = "2026-01-14T12:55:41.057Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ab/938368f8ce31a9787ecd4becb1e795954782e4312095daf8fd22420227c8/librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", size = 183224, upload-time = "2026-01-14T12:55:42.328Z" }, + { url = "https://files.pythonhosted.org/packages/3c/10/559c310e7a6e4014ac44867d359ef8238465fb499e7eb31b6bfe3e3f86f5/librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", size = 203541, upload-time = "2026-01-14T12:55:43.501Z" }, + { url = "https://files.pythonhosted.org/packages/f8/db/a0db7acdb6290c215f343835c6efda5b491bb05c3ddc675af558f50fdba3/librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", size = 40657, upload-time = "2026-01-14T12:55:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/72/e0/4f9bdc2a98a798511e81edcd6b54fe82767a715e05d1921115ac70717f6f/librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", size = 46835, upload-time = "2026-01-14T12:55:45.655Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3d/59c6402e3dec2719655a41ad027a7371f8e2334aa794ed11533ad5f34969/librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", size = 39885, upload-time = "2026-01-14T12:55:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/2481d80950b83085fb14ba3c595db56330d21bbc7d88a19f20165f3538db/librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", size = 59161, upload-time = "2026-01-14T12:55:48.45Z" }, + { url = "https://files.pythonhosted.org/packages/96/79/108df2cfc4e672336765d54e3ff887294c1cc36ea4335c73588875775527/librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", size = 61008, upload-time = "2026-01-14T12:55:49.527Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/30179898f9994a5637459d6e169b6abdc982012c0a4b2d4c26f50c06f911/librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", size = 187199, upload-time = "2026-01-14T12:55:50.587Z" }, + { url = "https://files.pythonhosted.org/packages/b4/da/f7563db55cebdc884f518ba3791ad033becc25ff68eb70902b1747dc0d70/librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", size = 198317, upload-time = "2026-01-14T12:55:51.991Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6c/4289acf076ad371471fa86718c30ae353e690d3de6167f7db36f429272f1/librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", size = 210334, upload-time = "2026-01-14T12:55:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7f/377521ac25b78ac0a5ff44127a0360ee6d5ddd3ce7327949876a30533daa/librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", size = 211031, upload-time = "2026-01-14T12:55:54.827Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/e1e96c3e20b23d00cf90f4aad48f0deb4cdfec2f0ed8380d0d85acf98bbf/librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", size = 204581, upload-time = "2026-01-14T12:55:56.811Z" }, + { url = "https://files.pythonhosted.org/packages/43/71/0f5d010e92ed9747e14bef35e91b6580533510f1e36a8a09eb79ee70b2f0/librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", size = 224731, upload-time = "2026-01-14T12:55:58.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, + { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, + { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/a4/3959e1c61c5ca9db7921e5fd115b344c29b9d57a5dadd87bef97963ca1a5/llvmlite-0.46.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4323177e936d61ae0f73e653e2e614284d97d14d5dd12579adc92b6c2b0597b0", size = 37232766, upload-time = "2025-12-08T18:14:34.765Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a5/a4d916f1015106e1da876028606a8e87fd5d5c840f98c87bc2d5153b6a2f/llvmlite-0.46.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a2d461cb89537b7c20feb04c46c32e12d5ad4f0896c9dfc0f60336219ff248e", size = 56275176, upload-time = "2025-12-08T18:14:37.944Z" }, + { url = "https://files.pythonhosted.org/packages/79/7f/a7f2028805dac8c1a6fae7bda4e739b7ebbcd45b29e15bf6d21556fcd3d5/llvmlite-0.46.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b1f6595a35b7b39c3518b85a28bf18f45e075264e4b2dce3f0c2a4f232b4a910", size = 55128629, upload-time = "2025-12-08T18:14:41.674Z" }, + { url = "https://files.pythonhosted.org/packages/b2/bc/4689e1ba0c073c196b594471eb21be0aa51d9e64b911728aa13cd85ef0ae/llvmlite-0.46.0-cp310-cp310-win_amd64.whl", hash = "sha256:e7a34d4aa6f9a97ee006b504be6d2b8cb7f755b80ab2f344dda1ef992f828559", size = 38138651, upload-time = "2025-12-08T18:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a1/2ad4b2367915faeebe8447f0a057861f646dbf5fbbb3561db42c65659cf3/llvmlite-0.46.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82f3d39b16f19aa1a56d5fe625883a6ab600d5cc9ea8906cca70ce94cabba067", size = 37232766, upload-time = "2025-12-08T18:14:48.836Z" }, + { url = "https://files.pythonhosted.org/packages/12/b5/99cf8772fdd846c07da4fd70f07812a3c8fd17ea2409522c946bb0f2b277/llvmlite-0.46.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a3df43900119803bbc52720e758c76f316a9a0f34612a886862dfe0a5591a17e", size = 56275175, upload-time = "2025-12-08T18:14:51.604Z" }, + { url = "https://files.pythonhosted.org/packages/38/f2/ed806f9c003563732da156139c45d970ee435bd0bfa5ed8de87ba972b452/llvmlite-0.46.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de183fefc8022d21b0aa37fc3e90410bc3524aed8617f0ff76732fc6c3af5361", size = 55128630, upload-time = "2025-12-08T18:14:55.107Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/8f5a37a65fc9b7b17408508145edd5f86263ad69c19d3574e818f533a0eb/llvmlite-0.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8b10bc585c58bdffec9e0c309bb7d51be1f2f15e169a4b4d42f2389e431eb93", size = 38138652, upload-time = "2025-12-08T18:14:58.171Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" }, + { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" }, + { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z" }, + { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, + { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mistune" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nbclient" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nbsphinx" +version = "0.9.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jinja2" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/d1/82081750f8a78ad0399c6ed831d42623b891904e8e7b8a75878225cf1dce/nbsphinx-0.9.8.tar.gz", hash = "sha256:d0765908399a8ee2b57be7ae881cf2ea58d66db3af7bbf33e6eb48f83bea5495", size = 417469, upload-time = "2025-11-28T17:41:02.336Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/78/843bcf0cf31f88d2f8a9a063d2d80817b1901657d83d65b89b3aa835732e/nbsphinx-0.9.8-py3-none-any.whl", hash = "sha256:92d95ee91784e56bc633b60b767a6b6f23a0445f891e24641ce3c3f004759ccf", size = 31961, upload-time = "2025-11-28T17:41:00.796Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "numba" +version = "0.63.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/60/0145d479b2209bd8fdae5f44201eceb8ce5a23e0ed54c71f57db24618665/numba-0.63.1.tar.gz", hash = "sha256:b320aa675d0e3b17b40364935ea52a7b1c670c9037c39cf92c49502a75902f4b", size = 2761666, upload-time = "2025-12-10T02:57:39.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/ce/5283d4ffa568f795bb0fd61ee1f0efc0c6094b94209259167fc8d4276bde/numba-0.63.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6d6bf5bf00f7db629305caaec82a2ffb8abe2bf45eaad0d0738dc7de4113779", size = 2680810, upload-time = "2025-12-10T02:56:55.269Z" }, + { url = "https://files.pythonhosted.org/packages/0f/72/a8bda517e26d912633b32626333339b7c769ea73a5c688365ea5f88fd07e/numba-0.63.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08653d0dfc9cc9c4c9a8fba29ceb1f2d5340c3b86c4a7e5e07e42b643bc6a2f4", size = 3739735, upload-time = "2025-12-10T02:56:57.922Z" }, + { url = "https://files.pythonhosted.org/packages/ca/17/1913b7c1173b2db30fb7a9696892a7c4c59aeee777a9af6859e9e01bac51/numba-0.63.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f09eebf5650246ce2a4e9a8d38270e2d4b0b0ae978103bafb38ed7adc5ea906e", size = 3446707, upload-time = "2025-12-10T02:56:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/b4/77/703db56c3061e9fdad5e79c91452947fdeb2ec0bdfe4affe9b144e7025e0/numba-0.63.1-cp310-cp310-win_amd64.whl", hash = "sha256:f8bba17421d865d8c0f7be2142754ebce53e009daba41c44cf6909207d1a8d7d", size = 2747374, upload-time = "2025-12-10T02:57:07.908Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/5f8614c165d2e256fbc6c57028519db6f32e4982475a372bbe550ea0454c/numba-0.63.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b33db00f18ccc790ee9911ce03fcdfe9d5124637d1ecc266f5ae0df06e02fec3", size = 2680501, upload-time = "2025-12-10T02:57:09.797Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9d/d0afc4cf915edd8eadd9b2ab5b696242886ee4f97720d9322650d66a88c6/numba-0.63.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d31ea186a78a7c0f6b1b2a3fe68057fdb291b045c52d86232b5383b6cf4fc25", size = 3744945, upload-time = "2025-12-10T02:57:11.697Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/d82f38f2ab73f3be6f838a826b545b80339762ee8969c16a8bf1d39395a8/numba-0.63.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed3bb2fbdb651d6aac394388130a7001aab6f4541837123a4b4ab8b02716530c", size = 3450827, upload-time = "2025-12-10T02:57:13.709Z" }, + { url = "https://files.pythonhosted.org/packages/18/3f/a9b106e93c5bd7434e65f044bae0d204e20aa7f7f85d72ceb872c7c04216/numba-0.63.1-cp311-cp311-win_amd64.whl", hash = "sha256:1ecbff7688f044b1601be70113e2fb1835367ee0b28ffa8f3adf3a05418c5c87", size = 2747262, upload-time = "2025-12-10T02:57:15.664Z" }, + { url = "https://files.pythonhosted.org/packages/14/9c/c0974cd3d00ff70d30e8ff90522ba5fbb2bcee168a867d2321d8d0457676/numba-0.63.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2819cd52afa5d8d04e057bdfd54367575105f8829350d8fb5e4066fb7591cc71", size = 2680981, upload-time = "2025-12-10T02:57:17.579Z" }, + { url = "https://files.pythonhosted.org/packages/cb/70/ea2bc45205f206b7a24ee68a159f5097c9ca7e6466806e7c213587e0c2b1/numba-0.63.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5cfd45dbd3d409e713b1ccfdc2ee72ca82006860254429f4ef01867fdba5845f", size = 3801656, upload-time = "2025-12-10T02:57:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/4f4ba4fd0f99825cbf3cdefd682ca3678be1702b63362011de6e5f71f831/numba-0.63.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69a599df6976c03b7ecf15d05302696f79f7e6d10d620367407517943355bcb0", size = 3501857, upload-time = "2025-12-10T02:57:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/af/fd/6540456efa90b5f6604a86ff50dabefb187e43557e9081adcad3be44f048/numba-0.63.1-cp312-cp312-win_amd64.whl", hash = "sha256:bbad8c63e4fc7eb3cdb2c2da52178e180419f7969f9a685f283b313a70b92af3", size = 2750282, upload-time = "2025-12-10T02:57:22.474Z" }, + { url = "https://files.pythonhosted.org/packages/57/f7/e19e6eff445bec52dde5bed1ebb162925a8e6f988164f1ae4b3475a73680/numba-0.63.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:0bd4fd820ef7442dcc07da184c3f54bb41d2bdb7b35bacf3448e73d081f730dc", size = 2680954, upload-time = "2025-12-10T02:57:24.145Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6c/1e222edba1e20e6b113912caa9b1665b5809433cbcb042dfd133c6f1fd38/numba-0.63.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53de693abe4be3bd4dee38e1c55f01c55ff644a6a3696a3670589e6e4c39cde2", size = 3809736, upload-time = "2025-12-10T02:57:25.836Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/590bad11a8b3feeac30a24d01198d46bdb76ad15c70d3a530691ce3cae58/numba-0.63.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81227821a72a763c3d4ac290abbb4371d855b59fdf85d5af22a47c0e86bf8c7e", size = 3508854, upload-time = "2025-12-10T02:57:27.438Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f5/3800384a24eed1e4d524669cdbc0b9b8a628800bb1e90d7bd676e5f22581/numba-0.63.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb227b07c2ac37b09432a9bda5142047a2d1055646e089d4a240a2643e508102", size = 2750228, upload-time = "2025-12-10T02:57:30.36Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/53be2aa8a55ee2608ebe1231789cbb217f6ece7f5e1c685d2f0752e95a5b/numba-0.63.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f180883e5508940cc83de8a8bea37fc6dd20fbe4e5558d4659b8b9bef5ff4731", size = 2681153, upload-time = "2025-12-10T02:57:32.016Z" }, + { url = "https://files.pythonhosted.org/packages/13/91/53e59c86759a0648282368d42ba732c29524a745fd555ed1fb1df83febbe/numba-0.63.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0938764afa82a47c0e895637a6c55547a42c9e1d35cac42285b1fa60a8b02bb", size = 3778718, upload-time = "2025-12-10T02:57:33.764Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/2be19eba50b0b7636f6d1f69dfb2825530537708a234ba1ff34afc640138/numba-0.63.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f90a929fa5094e062d4e0368ede1f4497d5e40f800e80aa5222c4734236a2894", size = 3478712, upload-time = "2025-12-10T02:57:35.518Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5f/4d0c9e756732577a52211f31da13a3d943d185f7fb90723f56d79c696caa/numba-0.63.1-cp314-cp314-win_amd64.whl", hash = "sha256:8d6d5ce85f572ed4e1a135dbb8c0114538f9dd0e3657eeb0bb64ab204cbe2a8f", size = 2752161, upload-time = "2025-12-10T02:57:37.12Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, +] + +[[package]] +name = "numpydoc" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/3c/dfccc9e7dee357fb2aa13c3890d952a370dd0ed071e0f7ed62ed0df567c1/numpydoc-1.10.0.tar.gz", hash = "sha256:3f7970f6eee30912260a6b31ac72bba2432830cd6722569ec17ee8d3ef5ffa01", size = 94027, upload-time = "2025-12-02T16:39:12.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/5e/3a6a3e90f35cea3853c45e5d5fb9b7192ce4384616f932cf7591298ab6e1/numpydoc-1.10.0-py3-none-any.whl", hash = "sha256:3149da9874af890bcc2a82ef7aae5484e5aa81cb2778f08e3c307ba6d963721b", size = 69255, upload-time = "2025-12-02T16:39:11.561Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, + { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, + { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, + { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, + { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pooch" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "platformdirs" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788, upload-time = "2026-01-30T19:15:09.649Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pygments-styles" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/2c/3886ed4783dd78bb62ccab7d43380f526a7e2ff0db8c77d9c87559b2f5de/pygments_styles-0.3.0.tar.gz", hash = "sha256:67746b8fc6ff72c1179ca4d9a8bc89c7f54c196c2ff9d087f07392cd6fde3ecf", size = 15258, upload-time = "2025-11-04T13:15:23.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/64/7e0266f0c541e26df86c31d2add9be3dd9914ae83785ce0aba7cbb693667/pygments_styles-0.3.0-py3-none-any.whl", hash = "sha256:c6c45e9939eb7590345bc9084113bac46c45f12b009d13422be02e80e84a034c", size = 36617, upload-time = "2025-11-04T13:15:21.989Z" }, +] + +[[package]] +name = "pylops" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/e0/28d300177adf19f28fef2b95b3aabe01d39cedd79a739a3139dfb63c04be/pylops-2.6.0.tar.gz", hash = "sha256:4fd0fd66fa2eb0270a3c044a04b113d4636be029888066dabb6f403fc98ac40f", size = 269730, upload-time = "2025-11-24T20:34:07.709Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/2d/efada7f1c48577c33392b05bf6479e890fc968d5ca158ed185fd4bc9e34b/pylops-2.6.0-py3-none-any.whl", hash = "sha256:d918764249d819924b7802abfdcaae972b4a1c077c26ed1f93e2b5f214440c72", size = 348607, upload-time = "2025-11-24T20:34:06.351Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pyproximal" +source = { editable = "." } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pylops" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions" }, +] + +[package.optional-dependencies] +advanced = [ + { name = "llvmlite" }, + { name = "numba" }, +] + +[package.dev-dependencies] +dev = [ + { name = "autopep8" }, + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, +] +doc = [ + { name = "bm3d" }, + { name = "bm4d" }, + { name = "image" }, + { name = "matplotlib" }, + { name = "nbsphinx" }, + { name = "numpydoc" }, + { name = "pooch" }, + { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "shibuya" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-gallery" }, + { name = "sphinx-iconify" }, + { name = "sphinxemoji" }, +] + +[package.metadata] +requires-dist = [ + { name = "llvmlite", marker = "extra == 'advanced'" }, + { name = "numba", marker = "extra == 'advanced'" }, + { name = "numpy", specifier = ">=1.21.0" }, + { name = "pylops", specifier = ">=2.0.0" }, + { name = "scipy", specifier = ">=1.11.0" }, + { name = "typing-extensions" }, +] +provides-extras = ["advanced"] + +[package.metadata.requires-dev] +dev = [ + { name = "autopep8" }, + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, +] +doc = [ + { name = "bm3d", specifier = "<4.2.4" }, + { name = "bm4d", specifier = "<4.2.4" }, + { name = "image" }, + { name = "matplotlib" }, + { name = "nbsphinx" }, + { name = "numpydoc" }, + { name = "pooch" }, + { name = "scikit-image" }, + { name = "shibuya" }, + { name = "sphinx" }, + { name = "sphinx-design" }, + { name = "sphinx-gallery" }, + { name = "sphinx-iconify" }, + { name = "sphinxemoji" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, + { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pywavelets" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/45/bfaaab38545a33a9f06c61211fc3bea2e23e8a8e00fedeb8e57feda722ff/pywavelets-1.8.0.tar.gz", hash = "sha256:f3800245754840adc143cbc29534a1b8fc4b8cff6e9d403326bd52b7bb5c35aa", size = 3935274, upload-time = "2024-12-04T19:54:20.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/7e/c5e398f25c70558ca195dd4144ee004666401f6167084c1e76059d7e68d8/pywavelets-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f5c86fcb203c8e61d1f3d4afbfc08d626c64e4e3708207315577264c724632bf", size = 4323291, upload-time = "2024-12-04T19:53:01.836Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d7/2fc8067c3520ce25f7632b0f47b89d1b75653cab804a42700e95126f2679/pywavelets-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafb5fa126277e1690c3d6329287122fc08e4d25a262ce126e3d81b1f5709308", size = 4291864, upload-time = "2024-12-04T19:53:04.659Z" }, + { url = "https://files.pythonhosted.org/packages/2f/17/a868aa26e45c104613d9069f9d8ec0123687cb6945062d274f20a3992436/pywavelets-1.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec23dfe6d5a3f4312b12456b8c546aa90a11c1138e425a885987505f0658ae0", size = 4447532, upload-time = "2024-12-04T19:53:06.383Z" }, + { url = "https://files.pythonhosted.org/packages/53/7a/7f5889a57177e2b1182080fc2c52236d1e03a0fad5e0b3d7c5312070c0be/pywavelets-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:880a0197e9fa108939af50a95e97c1bf9b7d3e148e0fad92ea60a9ed8c8947c0", size = 4487695, upload-time = "2024-12-04T19:53:08.84Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e6/04d76d93c158919ef0d8e1d40d1d453168305031eca6733fdc844f7cbb07/pywavelets-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bfa833d08b60d0bf53a7939fbbf3d98015dd34efe89cbe4e53ced880d085fc1", size = 4473752, upload-time = "2024-12-04T19:53:10.848Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a7/42ea5bbb6055abd312e45b27d931200fd6eed5414a87ec5d62020a4c651b/pywavelets-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e10c3fc7f4a796e94da4bca9871be2186a7bb7a3b3536a0ca9376d84263140f0", size = 4504191, upload-time = "2024-12-04T19:53:13.912Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7e/52df87a9e77adfb12c1b8be79a2053f2eb4c2507dec96ebfd2333b15ff03/pywavelets-1.8.0-cp310-cp310-win32.whl", hash = "sha256:31baf4be6940fde72cc85663154360857ac1b93c251822deaf72bb804da95031", size = 4143794, upload-time = "2024-12-04T19:53:16.296Z" }, + { url = "https://files.pythonhosted.org/packages/01/e2/06e08230c26049740b2773952fbb12cc7186e5df655a73b1c30ba493e864/pywavelets-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:560c39f1ff8cb37f8b8ea4b7b6eb8a14f6926c11f5cf8c09f013a58f895ed5bc", size = 4214262, upload-time = "2024-12-04T19:53:17.998Z" }, + { url = "https://files.pythonhosted.org/packages/6c/8a/9f8e794120b55caa1c4ae8d72696111bc408251615f351a8e54a5d8c4d4e/pywavelets-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e8dd5be4faed994581a8a4b3c0169be20567a9346e523f0b57f903c8f6722bce", size = 4324170, upload-time = "2024-12-04T19:53:19.66Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b8/f6246be5c78e9fa73fcbba9ab4cbfe0d4dcb79ea5491f28d673a53466134/pywavelets-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d8abaf7c120b151ef309c9ff57e0a44ba9febf49045056dbc1577526ecec6c8", size = 4294254, upload-time = "2024-12-04T19:53:21.767Z" }, + { url = "https://files.pythonhosted.org/packages/2c/dc/ba1f212e9b43117ed28e0fd092e72e817790427400f88937ea742d260153/pywavelets-1.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b43a4c58707b1e8d941bec7f1d83e67c482278575ff0db3189d5c0dfae23a57", size = 4447178, upload-time = "2024-12-04T19:53:23.525Z" }, + { url = "https://files.pythonhosted.org/packages/58/10/e59c162a11d2fedb4454abbf7b74a52390aba5edc9605bf829bfa8708dac/pywavelets-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1aad0b97714e3079a2bfe48e4fb8ccd60778d0427e9ee5e0a9ff922e6c61e4", size = 4486799, upload-time = "2024-12-04T19:53:25.238Z" }, + { url = "https://files.pythonhosted.org/packages/03/ee/90c3d0a0a3bda74e6e097e4c06bff9446ff2a4c90b8617aaf4902c46966b/pywavelets-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0e1db96dcf3ce08156859df8b359e9ff66fa15061a1b90e70e020bf4cd077a0", size = 4486403, upload-time = "2024-12-04T19:53:26.954Z" }, + { url = "https://files.pythonhosted.org/packages/05/54/58b87f8b636a9f044f3f9814d2ec696cf25f3b33af97c11811f13c364085/pywavelets-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e62c8fb52ab0e8ff212fff9acae681a8f12d68b76c36fe24cc48809d5b6825ba", size = 4515011, upload-time = "2024-12-04T19:53:28.832Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d0/f755cee11ff20668114942d0e777e2b502a8e4665e1fdb2553b587aac637/pywavelets-1.8.0-cp311-cp311-win32.whl", hash = "sha256:bf327528d10de471b04bb725c4e10677fac5a49e13d41bf0d0b3a1f6d7097abf", size = 4139934, upload-time = "2024-12-04T19:53:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0b/f4b92d4f00565280ea3e62a8e3dc81a667d67ed7bd59232f2f18d55f9aff/pywavelets-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3814d354dd109e244ffaac3d480d29a5202212fe24570c920268237c8d276f95", size = 4214321, upload-time = "2024-12-04T19:53:33.183Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8b/4870f11559307416470158a5aa6f61e5c2a910f1645a7a836ffae580b7ad/pywavelets-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3f431c9e2aff1a2240765eff5e804975d0fcc24c82d6f3d4271243f228e5963b", size = 4326187, upload-time = "2024-12-04T19:53:35.19Z" }, + { url = "https://files.pythonhosted.org/packages/c4/35/66835d889fd7fbf3119c7a9bd9d9bd567fc0bb603dfba408e9226db7cb44/pywavelets-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e39b0e2314e928cb850ee89b9042733a10ea044176a495a54dc84d2c98407a51", size = 4295428, upload-time = "2024-12-04T19:53:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/63/1c/42e5130226538c70d4bbbaee00eb1bc06ec3287f7ea43d5fcf85bfc761ce/pywavelets-1.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cae701117f5c7244b7c8d48b9e92a0289637cdc02a9c205e8be83361f0c11fae", size = 4421259, upload-time = "2024-12-04T19:53:39.119Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c5/1ce93657432e22a5debc21e8b52ec6980f819ecb7fa727bb86744224d967/pywavelets-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649936baee933e80083788e0adc4d8bc2da7cdd8b10464d3b113475be2cc5308", size = 4447650, upload-time = "2024-12-04T19:53:41.589Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d6/b54ef30daca71824f811f9d2322a978b0a58d27674b8e3af6520f67e9ec6/pywavelets-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c68e9d072c536bc646e8bdce443bb1826eeb9aa21b2cb2479a43954dea692a3", size = 4448538, upload-time = "2024-12-04T19:53:44.308Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/1688b790e55674667ad644262f174405c2c9873cb13e773432e78b1b33e4/pywavelets-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:63f67fa2ee1610445de64f746fb9c1df31980ad13d896ea2331fc3755f49b3ae", size = 4485228, upload-time = "2024-12-04T19:53:46.778Z" }, + { url = "https://files.pythonhosted.org/packages/c9/9b/69de31c3b663dadd76d1da6bf8af68d8cefff55df8e880fe96a94bb8c9ac/pywavelets-1.8.0-cp312-cp312-win32.whl", hash = "sha256:4b3c2ab669c91e3474fd63294355487b7dd23f0b51d32f811327ddf3546f4f3d", size = 4134850, upload-time = "2024-12-04T19:53:49.101Z" }, + { url = "https://files.pythonhosted.org/packages/1c/88/9e2aa9d5fde08bfc0fb18ffb1b5307c1ed49c24930b4147e5f48571a7251/pywavelets-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:810a23a631da596fef7196ddec49b345b1aab13525bb58547eeebe1769edbbc1", size = 4210786, upload-time = "2024-12-04T19:53:51.546Z" }, + { url = "https://files.pythonhosted.org/packages/94/73/7ff347d77c6bda11330565050c3346c54bc210086380abeb84e402c1c9cd/pywavelets-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:441ba45c8dff8c6916dbe706958d0d7f91da675695ca0c0d75e483f6f52d0a12", size = 4321474, upload-time = "2024-12-04T19:53:53.369Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/c58937ebbca1aba3475ca5ee63c7bcebf09f3c93891ae5942eaec7e95707/pywavelets-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:24bb282bab09349d9d128ed0536fa50fff5c2147891971a69c2c36155dfeeeac", size = 4291502, upload-time = "2024-12-04T19:53:55.396Z" }, + { url = "https://files.pythonhosted.org/packages/da/55/87b4ad6128b2e85985908e958e856e0b680cdcc03cc490e2cc995164b13a/pywavelets-1.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:426ff3799446cb4da1db04c2084e6e58edfe24225596805665fd39c14f53dece", size = 4412669, upload-time = "2024-12-04T19:53:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1a/bfca9eab23bd7b27843b0ce95c47796033a7b2c93048315f5fc5d6ac6428/pywavelets-1.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0607a9c085b8285bc0d04e33d461a6c80f8c325389221ffb1a45141861138e", size = 4454604, upload-time = "2024-12-04T19:53:59.105Z" }, + { url = "https://files.pythonhosted.org/packages/c3/23/9ce38829f57159e812c469c4f9d7b5a16c1ba922c1802985e8c504468206/pywavelets-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d31c36a39110e8fcc7b1a4a11cfed7d22b610c285d3e7f4fe73ec777aa49fa39", size = 4445507, upload-time = "2024-12-04T19:54:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d2/e78a976b0600a6ef7a70f4430122d6ad11b3e1cbda3c8b3565661d094678/pywavelets-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa7c68ed1e5bab23b1bafe60ccbcf709b878652d03de59e961baefa5210fcd0a", size = 4479078, upload-time = "2024-12-04T19:54:02.46Z" }, + { url = "https://files.pythonhosted.org/packages/85/4d/1c4f870010368f3aeb0bdd72929376a1988e4a122e76545bd8c56e549c96/pywavelets-1.8.0-cp313-cp313-win32.whl", hash = "sha256:2c6b359b55d713ef683e9da1529181b865a80d759881ceb9adc1c5742e4da4d8", size = 4133763, upload-time = "2024-12-04T19:54:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/0a709a5732e6cf9297fc87bf545cb879997cde204115f8c0cbc296c5bdd3/pywavelets-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dbebcfd55ea8a85b7fc8802d411e75337170422abf6e96019d7e46c394e80e5", size = 4209548, upload-time = "2024-12-04T19:54:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/de/2a/4cac0bba67d3bc0ad697d0680539864db0a6964c7ad953d8d9d887f360b3/pywavelets-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2e1c79784bebeafd3715c1bea6621daa2e2e6ed37b687719322e2078fb35bb70", size = 4335183, upload-time = "2024-12-04T19:54:08.349Z" }, + { url = "https://files.pythonhosted.org/packages/58/d1/3abe4cf34a35b09ad847f0e9a85f340c1988611222926d295fa8710659e7/pywavelets-1.8.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f489380c95013cc8fb3ef338f6d8c1a907125db453cc4dc739e2cca06fcd8b6", size = 4454723, upload-time = "2024-12-04T19:54:11.007Z" }, + { url = "https://files.pythonhosted.org/packages/d5/62/f05dd191232ae94e0b48509bb0ee65c9d45abf5e8f3612b09fd309b41384/pywavelets-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:06786201a91b5e74540f4f3c115c49a29190de2eb424823abbd3a1fd75ea3e28", size = 4472192, upload-time = "2024-12-04T19:54:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/20/6a/257c95ad1e0fd395cbccd4ffec0d01cc9b51a3bb91e67d8fa10ffebc9c72/pywavelets-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:f2877fb7b58c94211257dcf364b204d6ed259146fc87d5a90bf9d93c97af6226", size = 4183968, upload-time = "2024-12-04T19:54:15.099Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/7179fd6f87153f2e339171e8cfe9bf901398a89045eefd7a3911bb9b47ad/pywavelets-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ec5d723c3335ff8aa630fd4b14097077f12cc02893c91cafd60dd7b1730e780f", size = 4265431, upload-time = "2024-12-04T19:54:16.928Z" }, +] + +[[package]] +name = "pywavelets" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/75/50581633d199812205ea8cdd0f6d52f12a624886b74bf1486335b67f01ff/pywavelets-1.9.0.tar.gz", hash = "sha256:148d12203377772bea452a59211d98649c8ee4a05eff019a9021853a36babdc8", size = 3938340, upload-time = "2025-08-04T16:20:04.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/8b/ca700d0c174c3a4eec1fbb603f04374d1fed84255c2a9f487cfaa749c865/pywavelets-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54662cce4d56f0d6beaa6ebd34b2960f3aa4a43c83c9098a24729e9dc20a4be2", size = 4323640, upload-time = "2025-08-04T16:18:51.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f3/0fa57b6407ea9c4452b0bc182141256b9481b479ffbfc9d7fdb73afe193b/pywavelets-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d8ed4b4d1eab9347e8fe0c5b45008ce5a67225ce5b05766b8b1fa923a5f8b34", size = 4294938, upload-time = "2025-08-04T16:18:53.818Z" }, + { url = "https://files.pythonhosted.org/packages/ea/95/a998313c8459a57e488ff2b18e24be9e836aedda3aa3a1673197deeaa59a/pywavelets-1.9.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:862be65481fdfecfd84c6b0ca132ba571c12697a082068921bca5b5e039f1371", size = 4472829, upload-time = "2025-08-04T16:18:55.508Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8c/f316a153f7f89d2753df8a7371d15d0faab87e709fe02715dbc297c79385/pywavelets-1.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d76b7fa8fc500b09201d689b4f15bf5887e30ffbe2e1f338eb8470590eb4521a", size = 4524936, upload-time = "2025-08-04T16:18:57.146Z" }, + { url = "https://files.pythonhosted.org/packages/24/f7/89fdc1caef4b384a341a8e149253e23f36c1702bbb986a26123348624854/pywavelets-1.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa859d0b686a697c87a47e29319aebe44125f114a4f8c7e444832b921f52de5a", size = 4481475, upload-time = "2025-08-04T16:18:58.725Z" }, + { url = "https://files.pythonhosted.org/packages/82/53/b733fbfb71853e4a5c430da56e325a763562d65241dd785f0fadb67aed6a/pywavelets-1.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20e97b84a263003e2c7348bcf72beba96edda1a6169f072dc4e4d4ee3a6c7368", size = 4527994, upload-time = "2025-08-04T16:18:59.917Z" }, + { url = "https://files.pythonhosted.org/packages/ed/15/5f6a6e9fdad8341e42642ed622a5f3033da4ea9d426cc3e574ae418b4726/pywavelets-1.9.0-cp311-cp311-win32.whl", hash = "sha256:f8330cdbfa506000e63e79525716df888998a76414c5cd6ecd9a7e371191fb05", size = 4136109, upload-time = "2025-08-04T16:19:01.511Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62dbb4aea86ec9d79b283127c42cc896f4d4ff265a9aeb1337a7836dd550/pywavelets-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed10959a17df294ef55948dcc76367d59ec7b6aad67e38dd4e313d2fe3ad47b2", size = 4228321, upload-time = "2025-08-04T16:19:03.164Z" }, + { url = "https://files.pythonhosted.org/packages/5c/37/3fda13fb2518fdd306528382d6b18c116ceafefff0a7dccd28f1034f4dd2/pywavelets-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30baa0788317d3c938560c83fe4fc43817342d06e6c9662a440f73ba3fb25c9b", size = 4320835, upload-time = "2025-08-04T16:19:04.855Z" }, + { url = "https://files.pythonhosted.org/packages/36/65/a5549325daafc3eae4b52de076798839eaf529a07218f8fb18cccefe76a1/pywavelets-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df7436a728339696a7aa955c020ae65c85b0d9d2b5ff5b4cf4551f5d4c50f2c7", size = 4290469, upload-time = "2025-08-04T16:19:06.178Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/901bb756d37dfa56baa26ef4a3577aecfe9c55f50f51366fede322f8c91d/pywavelets-1.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07b26526db2476974581274c43a9c2447c917418c6bd03c8d305ad2a5cd9fac3", size = 4437717, upload-time = "2025-08-04T16:19:07.514Z" }, + { url = "https://files.pythonhosted.org/packages/0f/34/0f54dd9c288941294898877008bcb5c07012340cc9c5db9cff1bd185d449/pywavelets-1.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:573b650805d2f3c981a0e5ae95191c781a722022c37a0f6eba3fa7eae8e0ee17", size = 4483843, upload-time = "2025-08-04T16:19:08.857Z" }, + { url = "https://files.pythonhosted.org/packages/48/1f/cff6bb4ea64ff508d8cac3fe113c0aa95310a7446d9efa6829027cc2afdf/pywavelets-1.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3747ec804492436de6e99a7b6130480e53406d047e87dc7095ab40078a515a23", size = 4442236, upload-time = "2025-08-04T16:19:11.061Z" }, + { url = "https://files.pythonhosted.org/packages/ce/53/a3846eeefe0fb7ca63ae045f038457aa274989a15af793c1b824138caf98/pywavelets-1.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5163665686219c3f43fd5bbfef2391e87146813961dad0f86c62d4aed561f547", size = 4488077, upload-time = "2025-08-04T16:19:12.333Z" }, + { url = "https://files.pythonhosted.org/packages/f7/98/44852d2fe94455b72dece2db23562145179d63186a1c971125279a1c381f/pywavelets-1.9.0-cp312-cp312-win32.whl", hash = "sha256:80b8ab99f5326a3e724f71f23ba8b0a5b03e333fa79f66e965ea7bed21d42a2f", size = 4134094, upload-time = "2025-08-04T16:19:13.564Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a7/0d9ee3fe454d606e0f5c8e3aebf99d2ecddbfb681826a29397729538c8f1/pywavelets-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:92bfb8a117b8c8d3b72f2757a85395346fcbf37f50598880879ae72bd8e1c4b9", size = 4213900, upload-time = "2025-08-04T16:19:14.939Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/dec4e450675d62946ad975f5b4d924437df42d2fae46e91dfddda2de0f5a/pywavelets-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:74f8455c143818e4b026fc67b27fd82f38e522701b94b8a6d1aaf3a45fcc1a25", size = 4316201, upload-time = "2025-08-04T16:19:16.259Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0c/b54b86596c0df68027e48c09210e907e628435003e77048384a2dd6767e3/pywavelets-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c50320fe0a4a23ddd8835b3dc9b53b09ee05c7cc6c56b81d0916f04fc1649070", size = 4286838, upload-time = "2025-08-04T16:19:17.92Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9c/333969c3baad8af2e7999e83addcb7bb1d1fd48e2d812fb27e2e89582cb1/pywavelets-1.9.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6e059265223ed659e5214ab52a84883c88ddf3decbf08d7ec6abb8e4c5ed7be", size = 4430753, upload-time = "2025-08-04T16:19:19.529Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/a24c6ff03b026b826ad7b9267bd63cd34ce026795a0302f8a5403840b8e7/pywavelets-1.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae10ed46c139c7ddb8b1249cfe0989f8ccb610d93f2899507b1b1573a0e424b5", size = 4491315, upload-time = "2025-08-04T16:19:20.717Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/e3fbb502fca3469e51ced4f1e1326364c338be91edc5db5a8ddd26b303fa/pywavelets-1.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8f8b1cc2df012401cb837ee6fa2f59607c7b4fe0ff409d9a4f6906daf40dc86", size = 4437654, upload-time = "2025-08-04T16:19:22.359Z" }, + { url = "https://files.pythonhosted.org/packages/92/44/c9b25084048d9324881a19b88e0969a4141bcfdc1d218f1b4b680b7af1c1/pywavelets-1.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db43969c7a8fbb17693ecfd14f21616edc3b29f0e47a49b32fa4127c01312a67", size = 4496435, upload-time = "2025-08-04T16:19:23.842Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b6/b27ec18c72b1dee3314e297af39c5f8136d43cc130dd93cb6c178ca820e5/pywavelets-1.9.0-cp313-cp313-win32.whl", hash = "sha256:9e7d60819d87dcd6c68a2d1bc1d37deb1f4d96607799ab6a25633ea484dcda41", size = 4132709, upload-time = "2025-08-04T16:19:25.415Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/78ef3f9fb36cdb16ee82371d22c3a7c89eeb79ec8c9daef6222060da6c79/pywavelets-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:0d70da9d7858c869e24dc254f16a61dc09d8a224cad85a10c393b2eccddeb126", size = 4213377, upload-time = "2025-08-04T16:19:26.875Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cd/ca0d9db0ff29e3843f6af60c2f5eb588794e05ca8eeb872a595867b1f3f5/pywavelets-1.9.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dc85f44c38d76a184a1aa2cb038f802c3740428c9bb877525f4be83a223b134", size = 4354336, upload-time = "2025-08-04T16:19:28.745Z" }, + { url = "https://files.pythonhosted.org/packages/82/d6/70afefcc1139f37d02018a3b1dba3b8fc87601bb7707d9616b7f7a76e269/pywavelets-1.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7acf6f950c6deaecd210fbff44421f234a8ca81eb6f4da945228e498361afa9d", size = 4335721, upload-time = "2025-08-04T16:19:30.371Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3a/713f731b9ed6df0c36269c8fb62be8bb28eb343b9e26b13d6abda37bce38/pywavelets-1.9.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:144d4fc15c98da56654d0dca2d391b812b8d04127b194a37ad4a497f8e887141", size = 4418702, upload-time = "2025-08-04T16:19:31.743Z" }, + { url = "https://files.pythonhosted.org/packages/44/e8/f801eb4b5f7a316ba20054948c5d6b27b879c77fab2674942e779974bd86/pywavelets-1.9.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1aa3729585408a979d655736f74b995b511c86b9be1544f95d4a3142f8f4b8b5", size = 4470023, upload-time = "2025-08-04T16:19:32.963Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/44b002cb16f2a392f2082308dd470b3f033fa4925d3efa7c46f790ce895a/pywavelets-1.9.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e0e24ad6b8eb399c49606dd1fcdcbf9749ad7f6d638be3fe6f59c1f3098821e2", size = 4426498, upload-time = "2025-08-04T16:19:34.151Z" }, + { url = "https://files.pythonhosted.org/packages/91/fe/2b70276ede7878c5fe8356ca07574db5da63e222ce39a463e84bfad135e8/pywavelets-1.9.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3830e6657236b53a3aae20c735cccead942bb97c54bbca9e7d07bae01645fe9c", size = 4477528, upload-time = "2025-08-04T16:19:35.932Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ed/d58b540c15e36508cfeded7b0d39493e811b0dce18d9d4e6787fb2e89685/pywavelets-1.9.0-cp313-cp313t-win32.whl", hash = "sha256:81bb65facfbd7b50dec50450516e72cdc51376ecfdd46f2e945bb89d39bfb783", size = 4186493, upload-time = "2025-08-04T16:19:37.198Z" }, + { url = "https://files.pythonhosted.org/packages/84/b2/12a849650d618a86bbe4d8876c7e20a7afe59a8cad6f49c57eca9af26dfa/pywavelets-1.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:47d52cf35e2afded8cfe1133663f6f67106a3220b77645476ae660ad34922cb4", size = 4274821, upload-time = "2025-08-04T16:19:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1f/18c82122547c9eec2232d800b02ada1fbd30ce2136137b5738acca9d653e/pywavelets-1.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:53043d2f3f4e55a576f51ac594fe33181e1d096d958e01524db5070eb3825306", size = 4314440, upload-time = "2025-08-04T16:19:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e1/1c92ac6b538ef5388caf1a74af61cf6af16ea6d14115bb53357469cb38d6/pywavelets-1.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bc36b42b1b125fd9cb56e7956b22f8d0f83c1093f49c77fc042135e588c799", size = 4290162, upload-time = "2025-08-04T16:19:41.322Z" }, + { url = "https://files.pythonhosted.org/packages/96/d3/d856a2cac8069c20144598fa30a43ca40b5df2e633230848a9a942faf04a/pywavelets-1.9.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08076eb9a182ddc6054ac86868fb71df6267c341635036dc63d20bdbacd9ad7e", size = 4437162, upload-time = "2025-08-04T16:19:42.556Z" }, + { url = "https://files.pythonhosted.org/packages/c9/54/777e0495acd4fb008791e84889be33d6e7fc8af095b441d939390b7d2491/pywavelets-1.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ee1ee7d80f88c64b8ec3b5021dd1e94545cc97f0cd479fb51aa7b10f6def08e", size = 4498169, upload-time = "2025-08-04T16:19:43.791Z" }, + { url = "https://files.pythonhosted.org/packages/76/68/81b97f4d18491a18fbe17e06e2eee80a591ce445942f7b6f522de07813c5/pywavelets-1.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3226b6f62838a6ccd7782cb7449ee5d8b9d61999506c1d9b03b2baf41b01b6fd", size = 4443318, upload-time = "2025-08-04T16:19:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/92/74/5147f2f0436f7aa131cb1bc13dba32ef5f3862748ae1c7366b4cde380362/pywavelets-1.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fb7f4b11d18e2db6dd8deee7b3ce8343d45f195f3f278c2af6e3724b1b93a24", size = 4503294, upload-time = "2025-08-04T16:19:46.632Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d4/af998cc71e869919e0ab45471bd43e91d055ac7bc3ce6f56cc792c9b6bc8/pywavelets-1.9.0-cp314-cp314-win32.whl", hash = "sha256:9902d9fc9812588ab2dce359a1307d8e7f002b53a835640e2c9388fe62a82fd4", size = 4144478, upload-time = "2025-08-04T16:19:47.974Z" }, + { url = "https://files.pythonhosted.org/packages/7d/66/1d071eae5cc3e3ad0e45334462f8ce526a79767ccb759eb851aa5b78a73a/pywavelets-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:7e57792bde40e331d6cc65458e5970fd814dba18cfc4e9add9d051e901a7b7c7", size = 4227186, upload-time = "2025-08-04T16:19:49.57Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1f/da0c03ac99bd9d20409c0acf6417806d4cf333d70621da9f535dd0cf27fa/pywavelets-1.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b47c72fb4b76d665c4c598a5b621b505944e5b761bf03df9d169029aafcb652f", size = 4354391, upload-time = "2025-08-04T16:19:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/de9e225d8cc307fbb4fda88aefa79442775d5e27c58ee4d3c8a8580ceba6/pywavelets-1.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:969e369899e7eab546ea5d77074e4125082e6f9dad71966499bf5dee3758be55", size = 4335810, upload-time = "2025-08-04T16:19:52.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/3b/336761359d07cd44a4233ca854704ff2a9e78d285879ccc82d254b9daa57/pywavelets-1.9.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8aeffd4f35036c1fade972a61454de5709a7a8fc9a7d177eefe3ac34d76962e5", size = 4422220, upload-time = "2025-08-04T16:19:54.068Z" }, + { url = "https://files.pythonhosted.org/packages/98/61/76ccc7ada127f14f65eda40e37407b344fd3713acfca7a94d7f0f67fe57d/pywavelets-1.9.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f63f400fcd4e7007529bd06a5886009760da35cd7e76bb6adb5a5fbee4ffeb8c", size = 4470156, upload-time = "2025-08-04T16:19:55.379Z" }, + { url = "https://files.pythonhosted.org/packages/e0/de/142ca27ee729cf64113c2560748fcf2bd45b899ff282d6f6f3c0e7f177bb/pywavelets-1.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a63bcb6b5759a7eb187aeb5e8cd316b7adab7de1f4b5a0446c9a6bcebdfc22fb", size = 4430167, upload-time = "2025-08-04T16:19:56.566Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5e/90b39adff710d698c00ba9c3125e2bec99dad7c5f1a3ba37c73a78a6689f/pywavelets-1.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9950eb7c8b942e9bfa53d87c7e45a420dcddbd835c4c5f1aca045a3f775c6113", size = 4477378, upload-time = "2025-08-04T16:19:58.162Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1a/89f5f4ebcb9d34d9b7b2ac0a868c8b6d8c78d699a36f54407a060cea0566/pywavelets-1.9.0-cp314-cp314t-win32.whl", hash = "sha256:097f157e07858a1eb370e0d9c1bd11185acdece5cca10756d6c3c7b35b52771a", size = 4209132, upload-time = "2025-08-04T16:20:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/68/d2/a8065103f5e2e613b916489e6c85af6402a1ec64f346d1429e2d32cb8d03/pywavelets-1.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3b6ff6ba4f625d8c955f68c2c39b0a913776d406ab31ee4057f34ad4019fb33b", size = 4306793, upload-time = "2025-08-04T16:20:02.934Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version < '3.11'" }, + { name = "lazy-loader", marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pillow", marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "tifffile", version = "2025.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922, upload-time = "2025-02-18T18:04:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698, upload-time = "2025-02-18T18:04:15.362Z" }, + { url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634, upload-time = "2025-02-18T18:04:18.496Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545, upload-time = "2025-02-18T18:04:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908, upload-time = "2025-02-18T18:04:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version >= '3.11'" }, + { name = "lazy-loader", marker = "python_full_version >= '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pillow", marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "tifffile", version = "2026.1.28", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" }, + { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, + { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, + { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, + { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, + { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, + { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, + { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, + { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, + { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, + { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, + { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, + { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, + { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, + { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/4b/c89c131aa87cad2b77a54eb0fb94d633a842420fa7e919dc2f922037c3d8/scipy-1.17.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2abd71643797bd8a106dff97894ff7869eeeb0af0f7a5ce02e4227c6a2e9d6fd", size = 31381316, upload-time = "2026-01-10T21:24:33.42Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5f/a6b38f79a07d74989224d5f11b55267714707582908a5f1ae854cf9a9b84/scipy-1.17.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:ef28d815f4d2686503e5f4f00edc387ae58dfd7a2f42e348bb53359538f01558", size = 27966760, upload-time = "2026-01-10T21:24:38.911Z" }, + { url = "https://files.pythonhosted.org/packages/c1/20/095ad24e031ee8ed3c5975954d816b8e7e2abd731e04f8be573de8740885/scipy-1.17.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:272a9f16d6bb4667e8b50d25d71eddcc2158a214df1b566319298de0939d2ab7", size = 20138701, upload-time = "2026-01-10T21:24:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/89/11/4aad2b3858d0337756f3323f8960755704e530b27eb2a94386c970c32cbe/scipy-1.17.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:7204fddcbec2fe6598f1c5fdf027e9f259106d05202a959a9f1aecf036adc9f6", size = 22480574, upload-time = "2026-01-10T21:24:47.266Z" }, + { url = "https://files.pythonhosted.org/packages/85/bd/f5af70c28c6da2227e510875cadf64879855193a687fb19951f0f44cfd6b/scipy-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc02c37a5639ee67d8fb646ffded6d793c06c5622d36b35cfa8fe5ececb8f042", size = 32862414, upload-time = "2026-01-10T21:24:52.566Z" }, + { url = "https://files.pythonhosted.org/packages/ef/df/df1457c4df3826e908879fe3d76bc5b6e60aae45f4ee42539512438cfd5d/scipy-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dac97a27520d66c12a34fd90a4fe65f43766c18c0d6e1c0a80f114d2260080e4", size = 35112380, upload-time = "2026-01-10T21:24:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bb/88e2c16bd1dd4de19d80d7c5e238387182993c2fb13b4b8111e3927ad422/scipy-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb7446a39b3ae0fe8f416a9a3fdc6fba3f11c634f680f16a239c5187bc487c0", size = 34922676, upload-time = "2026-01-10T21:25:04.287Z" }, + { url = "https://files.pythonhosted.org/packages/02/ba/5120242cc735f71fc002cff0303d536af4405eb265f7c60742851e7ccfe9/scipy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:474da16199f6af66601a01546144922ce402cb17362e07d82f5a6cf8f963e449", size = 37507599, upload-time = "2026-01-10T21:25:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/08629657ac6c0da198487ce8cd3de78e02cfde42b7f34117d56a3fe249dc/scipy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:255c0da161bd7b32a6c898e7891509e8a9289f0b1c6c7d96142ee0d2b114c2ea", size = 36380284, upload-time = "2026-01-10T21:25:15.632Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4a/465f96d42c6f33ad324a40049dfd63269891db9324aa66c4a1c108c6f994/scipy-1.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b0ac3ad17fa3be50abd7e69d583d98792d7edc08367e01445a1e2076005379", size = 24370427, upload-time = "2026-01-10T21:25:20.514Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, + { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, + { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, + { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, + { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, + { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, + { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, + { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, + { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2d/51006cd369b8e7879e1c630999a19d1fbf6f8b5ed3e33374f29dc87e53b3/scipy-1.17.0-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:c17514d11b78be8f7e6331b983a65a7f5ca1fd037b95e27b280921fe5606286a", size = 31346803, upload-time = "2026-01-10T21:28:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2e/2349458c3ce445f53a6c93d4386b1c4c5c0c540917304c01222ff95ff317/scipy-1.17.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:4e00562e519c09da34c31685f6acc3aa384d4d50604db0f245c14e1b4488bfa2", size = 27967182, upload-time = "2026-01-10T21:29:04.107Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7c/df525fbfa77b878d1cfe625249529514dc02f4fd5f45f0f6295676a76528/scipy-1.17.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7df7941d71314e60a481e02d5ebcb3f0185b8d799c70d03d8258f6c80f3d467", size = 20139125, upload-time = "2026-01-10T21:29:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/33/11/fcf9d43a7ed1234d31765ec643b0515a85a30b58eddccc5d5a4d12b5f194/scipy-1.17.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:aabf057c632798832f071a8dde013c2e26284043934f53b00489f1773b33527e", size = 22443554, upload-time = "2026-01-10T21:29:15.888Z" }, + { url = "https://files.pythonhosted.org/packages/80/5c/ea5d239cda2dd3d31399424967a24d556cf409fbea7b5b21412b0fd0a44f/scipy-1.17.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a38c3337e00be6fd8a95b4ed66b5d988bac4ec888fd922c2ea9fe5fb1603dd67", size = 32757834, upload-time = "2026-01-10T21:29:23.406Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7e/8c917cc573310e5dc91cbeead76f1b600d3fb17cf0969db02c9cf92e3cfa/scipy-1.17.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73", size = 34995775, upload-time = "2026-01-10T21:29:31.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/43/176c0c3c07b3f7df324e7cdd933d3e2c4898ca202b090bd5ba122f9fe270/scipy-1.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f2a4942b0f5f7c23c7cd641a0ca1955e2ae83dedcff537e3a0259096635e186b", size = 34841240, upload-time = "2026-01-10T21:29:39.995Z" }, + { url = "https://files.pythonhosted.org/packages/44/8c/d1f5f4b491160592e7f084d997de53a8e896a3ac01cd07e59f43ca222744/scipy-1.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf133ced83889583156566d2bdf7a07ff89228fe0c0cb727f777de92092ec6b", size = 37394463, upload-time = "2026-01-10T21:29:48.723Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ec/42a6657f8d2d087e750e9a5dde0b481fd135657f09eaf1cf5688bb23c338/scipy-1.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061", size = 37053015, upload-time = "2026-01-10T21:30:51.418Z" }, + { url = "https://files.pythonhosted.org/packages/27/58/6b89a6afd132787d89a362d443a7bddd511b8f41336a1ae47f9e4f000dc4/scipy-1.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:9244608d27eafe02b20558523ba57f15c689357c85bdcfe920b1828750aa26eb", size = 24951312, upload-time = "2026-01-10T21:30:56.771Z" }, + { url = "https://files.pythonhosted.org/packages/e9/01/f58916b9d9ae0112b86d7c3b10b9e685625ce6e8248df139d0fcb17f7397/scipy-1.17.0-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1", size = 31706502, upload-time = "2026-01-10T21:29:56.326Z" }, + { url = "https://files.pythonhosted.org/packages/59/8e/2912a87f94a7d1f8b38aabc0faf74b82d3b6c9e22be991c49979f0eceed8/scipy-1.17.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1", size = 28380854, upload-time = "2026-01-10T21:30:01.554Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1c/874137a52dddab7d5d595c1887089a2125d27d0601fce8c0026a24a92a0b/scipy-1.17.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:eb2651271135154aa24f6481cbae5cc8af1f0dd46e6533fb7b56aa9727b6a232", size = 20552752, upload-time = "2026-01-10T21:30:05.93Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f0/7518d171cb735f6400f4576cf70f756d5b419a07fe1867da34e2c2c9c11b/scipy-1.17.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:c5e8647f60679790c2f5c76be17e2e9247dc6b98ad0d3b065861e082c56e078d", size = 22803972, upload-time = "2026-01-10T21:30:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/7c/74/3498563a2c619e8a3ebb4d75457486c249b19b5b04a30600dfd9af06bea5/scipy-1.17.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fb10d17e649e1446410895639f3385fd2bf4c3c7dfc9bea937bddcbc3d7b9ba", size = 32829770, upload-time = "2026-01-10T21:30:16.359Z" }, + { url = "https://files.pythonhosted.org/packages/48/d1/7b50cedd8c6c9d6f706b4b36fa8544d829c712a75e370f763b318e9638c1/scipy-1.17.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8547e7c57f932e7354a2319fab613981cde910631979f74c9b542bb167a8b9db", size = 35051093, upload-time = "2026-01-10T21:30:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/e2/82/a2d684dfddb87ba1b3ea325df7c3293496ee9accb3a19abe9429bce94755/scipy-1.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf", size = 34909905, upload-time = "2026-01-10T21:30:28.704Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5e/e565bd73991d42023eb82bb99e51c5b3d9e2c588ca9d4b3e2cc1d3ca62a6/scipy-1.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb55bb97d00f8b7ab95cb64f873eb0bf54d9446264d9f3609130381233483f", size = 37457743, upload-time = "2026-01-10T21:30:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/58/a8/a66a75c3d8f1fb2b83f66007d6455a06a6f6cf5618c3dc35bc9b69dd096e/scipy-1.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088", size = 37098574, upload-time = "2026-01-10T21:30:40.782Z" }, + { url = "https://files.pythonhosted.org/packages/56/a5/df8f46ef7da168f1bc52cd86e09a9de5c6f19cc1da04454d51b7d4f43408/scipy-1.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:031121914e295d9791319a1875444d55079885bbae5bdc9c5e0f2ee5f09d34ff", size = 25246266, upload-time = "2026-01-10T21:30:45.923Z" }, +] + +[[package]] +name = "shibuya" +version = "2026.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments-styles" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/b94cb04adbb984973fe83fd670dd066514610241d829723f678366e691d2/shibuya-2026.1.9.tar.gz", hash = "sha256:b389f10fd9c07b048e940f32d1e1ac096a2d49736389173ac771b37a10b51fdf", size = 86002, upload-time = "2026-01-09T02:19:14.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/ae/06d7dfc5633c7250fefc61fd624990aa2c37e3495c08a2f23968b1acb23e/shibuya-2026.1.9-py3-none-any.whl", hash = "sha256:b58a3cc6e5619c71d00fcf0be4a3060c87040c2a62a1b3f1a93a6a41ca8eaf45", size = 103389, upload-time = "2026-01-09T02:19:12.798Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version == '3.11.*'" }, + { name = "babel", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "imagesize", marker = "python_full_version == '3.11.*'" }, + { name = "jinja2", marker = "python_full_version == '3.11.*'" }, + { name = "packaging", marker = "python_full_version == '3.11.*'" }, + { name = "pygments", marker = "python_full_version == '3.11.*'" }, + { name = "requests", marker = "python_full_version == '3.11.*'" }, + { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, + { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.12'" }, + { name = "babel", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +] + +[[package]] +name = "sphinx-gallery" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pillow" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/14/9238ac61932299b38c20c7c37dbfe60348c0348ea4d400f9ef25875b3bf7/sphinx_gallery-0.20.0.tar.gz", hash = "sha256:70281510c6183d812d3595957005ccf555c5a793f207410f6cd16a25bf08d735", size = 473502, upload-time = "2025-12-02T15:51:37.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/fd/818a53d4da56ef2da7b08f77bb3a825635941d1fcc6b6a490995dec1a81c/sphinx_gallery-0.20.0-py3-none-any.whl", hash = "sha256:188b7456e269649945825661b76cdbfbf0b70c2cfd5b75c9a11fe52519879e4d", size = 458655, upload-time = "2025-12-02T15:51:35.311Z" }, +] + +[[package]] +name = "sphinx-iconify" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/4e/498964c2a6025e489b255015d3b5644aa93eb1dd1035324de18179af8abc/sphinx_iconify-0.3.0.tar.gz", hash = "sha256:7824ca694472d6babbe811e72cdcaf52b6e0ff4240e8cfda721f84f867605611", size = 3701, upload-time = "2025-12-18T05:19:03.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/6f/7dd3097d8ce08109cff3e63a771ca1ad795d8d63d8015eaa6fbd13f784fa/sphinx_iconify-0.3.0-py3-none-any.whl", hash = "sha256:b38ca4a66767c317bea31850a6f540ef79db4394eb4792961918c2d44b4a2c3a", size = 4068, upload-time = "2025-12-18T05:19:01.892Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sphinxemoji" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/05/d531d8ce28eeb364e900dab98b8e236cf664a1b7f5fa93c212a5e87313aa/sphinxemoji-0.3.2.tar.gz", hash = "sha256:814da89a9a7603b7e4d85c487c7381656fa83632f20065e5ed782ab1dbbb5083", size = 45999, upload-time = "2025-12-15T12:01:56.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/12/ff5e52d884b218ee9807e25ebb779a544809d7f0da9e01326a6d273c0aa1/sphinxemoji-0.3.2-py3-none-any.whl", hash = "sha256:0fb07a95bca5960a3b312bc05b65b0c3040456414a076c363f4ecaf546c6e09e", size = 45454, upload-time = "2025-12-15T12:01:55.635Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, +] + +[[package]] +name = "tifffile" +version = "2025.5.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/d0/18fed0fc0916578a4463f775b0fbd9c5fed2392152d039df2fb533bfdd5d/tifffile-2025.5.10.tar.gz", hash = "sha256:018335d34283aa3fd8c263bae5c3c2b661ebc45548fde31504016fcae7bf1103", size = 365290, upload-time = "2025-05-10T19:22:34.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/06/bd0a6097da704a7a7c34a94cfd771c3ea3c2f405dd214e790d22c93f6be1/tifffile-2025.5.10-py3-none-any.whl", hash = "sha256:e37147123c0542d67bc37ba5cdd67e12ea6fbe6e86c52bee037a9eb6a064e5ad", size = 226533, upload-time = "2025-05-10T19:22:27.279Z" }, +] + +[[package]] +name = "tifffile" +version = "2026.1.28" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/32/38498d2a1a5d70f33f6c3909bbad48557c9a54b0e33a9307ff06b6d416ba/tifffile-2026.1.28.tar.gz", hash = "sha256:537ae6466a8bb555c336108bb1878d8319d52c9c738041d3349454dea6956e1c", size = 374675, upload-time = "2026-01-29T05:17:24.992Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/19/529b28ca338c5a88315e71e672badc85eef89460c248c4164f6ce058f8c7/tifffile-2026.1.28-py3-none-any.whl", hash = "sha256:45b08a19cf603dd99952eff54a61519626a1912e4e2a4d355f05938fe4a6e9fd", size = 233011, upload-time = "2026-01-29T05:17:23.078Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] From 77646d528fe0cd49a15322cbf2a48cffbecf808c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 31 Jan 2026 20:31:32 +0000 Subject: [PATCH 07/72] minor: fix problem in build_uv --- .github/workflows/build_uv.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build_uv.yaml b/.github/workflows/build_uv.yaml index 0b9d16e..b4a5f9c 100644 --- a/.github/workflows/build_uv.yaml +++ b/.github/workflows/build_uv.yaml @@ -27,7 +27,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install dependencies and pyproximal - run: | - run: uv sync --locked --all-extras --all-groups + run: uv sync --locked --all-extras --all-groups - name: Test with pytest run: uv run pytest --color=yes pytests/ From 1416ada03d80fbd042eaa2a54cbcd1cf07f85f49 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 31 Jan 2026 20:35:22 +0000 Subject: [PATCH 08/72] doc: updated documentation theme --- docs/source/adding.rst | 4 +-- docs/source/api/index.rst | 4 +-- docs/source/changelog.rst | 4 +-- docs/source/conf.py | 17 +++++---- docs/source/contributing.rst | 8 +++-- docs/source/credits.rst | 4 +-- docs/source/index.rst | 8 ++--- docs/source/installation.rst | 68 ++++++++++++++++++++++++++---------- examples/README.txt | 4 +-- tutorials/README.txt | 4 +-- tutorials/deblending.py | 4 +-- 11 files changed, 79 insertions(+), 50 deletions(-) diff --git a/docs/source/adding.rst b/docs/source/adding.rst index fc46421..065dac2 100644 --- a/docs/source/adding.rst +++ b/docs/source/adding.rst @@ -2,9 +2,9 @@ .. _numpydoc-docstring: https://numpydoc.readthedocs.io/en/latest/ +|:paintbrush:| Implementing new operators +######################################### -Implementing new operators -========================== Users are welcome to create new operators and add them to the PyProximal library. In this tutorial, we will go through the key steps in the definition of an operator, using the diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index ddf5c47..cb1a60e 100755 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -1,7 +1,7 @@ .. _api: -PyProximal API -============== +|:wrench:| PyProximal API +========================= The Application Programming Interface (API) of PyProximal is composed of 3 elements: diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index a0e5561..d6719f0 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -1,7 +1,7 @@ .. _changlog: -Changelog -========= +|:newspaper_roll:| Changelog +############################ Version 0.11.1 -------------- diff --git a/docs/source/conf.py b/docs/source/conf.py index 15a95fe..012934c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,6 +22,8 @@ 'matplotlib.sphinxext.plot_directive', 'numpydoc', 'nbsphinx', + "sphinx_design", + "sphinx_iconify", 'sphinx_gallery.gen_gallery', 'sphinxemoji.sphinxemoji', ] @@ -87,7 +89,7 @@ # General information about the project year = datetime.date.today().year project = 'PyProximal' -copyright = '{}, Matteo Ravasi'.format(year) +copyright = "{}, PyLops Development Team".format(year) # Version version = __version__ @@ -103,7 +105,6 @@ html_last_updated_fmt = '%b %d, %Y' html_title = 'PyProximal' html_short_title = 'PyProximal' -html_logo = '_static/pyproximal.png' html_favicon = "_static/favicon.ico" html_extra_path = [] pygments_style = 'default' @@ -113,15 +114,12 @@ html_show_copyright = True # Theme config -html_theme = "pydata_sphinx_theme" +html_theme = "shibuya" html_theme_options = { + "accent_color": "teal", "github_url": "https://github.com/PyLops/pyproximal", - # 'logo_only': True, - # 'display_version': True, - "logo": { - "image_light": "pyproximal_b.png", - "image_dark": "pyproximal.png", - } + "light_logo": "_static/pyproximal_b.png", + "dark_logo": "_static/pyproximal.png", } html_css_files = [ "css/custom.css", @@ -157,6 +155,7 @@ } } + # Load the custom CSS files (needs sphinx >= 1.6 for this to work) def setup(app): app.add_css_file("style.css") diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 1c2e93a..bb4f911 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -1,7 +1,7 @@ .. _contributing: -Contributing -############ +|:heart:| Contributing +###################### Contributions are welcome and greatly appreciated! @@ -110,8 +110,10 @@ Before you submit a pull request, check that it meets these guidelines: 2. If the pull request adds functionality, the docs should be updated accordingly. 3. Ensure that the updated code passes all tests. + Project structure -################# +***************** + This repository is organized as follows: * **pyproximal**: Python library containing various proximal operators and solvers diff --git a/docs/source/credits.rst b/docs/source/credits.rst index a5ad34c..1ad0a69 100644 --- a/docs/source/credits.rst +++ b/docs/source/credits.rst @@ -1,7 +1,7 @@ .. _credits: -Contributors -============ +|:people_holding_hands:| Contributors +##################################### * `Matteo Ravasi `_, mrava87 * `Nick Luiken `_, NickLuiken diff --git a/docs/source/index.rst b/docs/source/index.rst index a30ef82..c5ace64 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -116,8 +116,8 @@ subclassing the :py:class:`pyproximal.ProxOperator` class and by implementing :hidden: :caption: Getting involved: - Implementing new operators - Contributing - Changelog - Credits + adding.rst + contributing.rst + changelog.rst + credits.rst diff --git a/docs/source/installation.rst b/docs/source/installation.rst index dd7ad62..c73e3ce 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -1,12 +1,12 @@ .. _installation: -Installation -############ +|:desktop_computer:| Installation +################################# Dependencies ************ -Our mandatory dependencies are limited to: +The mandatory dependencies of PyProximal are limited to: * Python 3.10 or greater * `NumPy `_ @@ -23,35 +23,65 @@ For learning, however, the standard installation is often good enough. Step-by-step installation for users *********************************** -Conda (recommended) -=================== -If using ``conda``, install our ``conda-forge`` distribution via: +From Package Manager +==================== +First install `pyproximal` with your package manager of choice. -.. code-block:: bash +.. tab-set:: - >> conda install --channel conda-forge pyproximal + .. tab-item:: :iconify:`devicon:anaconda` conda -Using the ``conda-forge`` distribution is recommended as all the dependencies (both required -and optional) will be automatically installed for you. + .. code-block:: bash -Pip -=== -If you are using ``pip``, and simply type the following command in your terminal -to install the PyPI distribution: + conda install --channel conda-forge pyproximal -.. code-block:: bash + Most of the dependencies (all required and some of the optional) are + automatically installed for you. + + .. tab-item:: :iconify:`devicon:pypi` pip + + .. code-block:: bash + + pip install pyproximal + + Only the *required* dependencies are installed. To install + some of the optional dependencies, run: + + .. code-block:: bash + + pip install "pyproximal[advanced]" + + .. tab-item:: :iconify:`material-icon-theme:uv` uv - >> pip install pyproximal + .. code-block:: bash + + uv add pyproximal + + Only the *required* dependencies are installed. To install + some of the optional dependencies, run: + + .. code-block:: bash + + uv add "pyproximal[advanced]" -Note that when installing via ``pip``, only *required* dependencies are installed. From Source =========== To access the latest source from github: -.. code-block:: bash +.. tab-set:: + + .. tab-item:: :iconify:`devicon:pypi` pip + + .. code-block:: bash + + pip install https://github.com/PyLops/pyproximal.git@dev + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash - >> pip install https://github.com/PyLops/pyproximal.git@dev + uv add git+https://github.com/PyLops/pyproximal.git --branch dev .. _DevInstall: diff --git a/examples/README.txt b/examples/README.txt index 3e480b0..3c7122a 100755 --- a/examples/README.txt +++ b/examples/README.txt @@ -1,6 +1,6 @@ .. _general_examples: -Gallery -------- +|:framed_picture:| Gallery +-------------------------- Below is a gallery of examples which use PyProximal operators and utilities. diff --git a/tutorials/README.txt b/tutorials/README.txt index cc1d377..13b8892 100755 --- a/tutorials/README.txt +++ b/tutorials/README.txt @@ -1,4 +1,4 @@ .. _tutorials: -Tutorials ---------- +|:books:| Tutorials +------------------- diff --git a/tutorials/deblending.py b/tutorials/deblending.py index 59151d8..af8c116 100644 --- a/tutorials/deblending.py +++ b/tutorials/deblending.py @@ -17,9 +17,7 @@ :math:`\mathbf{S}` is a patch-FK transform, and :math:`i_{\mathbf{d}^b-\boldsymbol\Phi \mathbf{d}}` is the indicator function of the :py:class:`pyproximal.AffineSet`. -References ----------- -1. M. Ravasi, "Seismic deblending with a hard data constraint", IMAGE, 2024. +.. [1] M. Ravasi, "Seismic deblending with a hard data constraint", IMAGE, 2024. """ From 932dd4e83a3014c680a0d020c72d97c38ad85480 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 31 Jan 2026 20:58:41 +0000 Subject: [PATCH 09/72] ci: added py3.13 to GAs --- .github/workflows/build.yaml | 2 +- .github/workflows/build_uv.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index abc3f9f..ea19b1e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: strategy: matrix: platform: [ ubuntu-latest, macos-latest ] - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: ${{ matrix.platform }} steps: diff --git a/.github/workflows/build_uv.yaml b/.github/workflows/build_uv.yaml index b4a5f9c..4fd9134 100644 --- a/.github/workflows/build_uv.yaml +++ b/.github/workflows/build_uv.yaml @@ -13,7 +13,7 @@ jobs: strategy: matrix: platform: [ ubuntu-latest, macos-latest ] - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: ${{ matrix.platform }} steps: From d6ea3b456c8db75828c8cde90dd6016fe6cfe668 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 31 Jan 2026 21:01:45 +0000 Subject: [PATCH 10/72] ci: added py3.14 to GAs --- .github/workflows/build.yaml | 2 +- .github/workflows/build_uv.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ea19b1e..8021aee 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: strategy: matrix: platform: [ ubuntu-latest, macos-latest ] - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ${{ matrix.platform }} steps: diff --git a/.github/workflows/build_uv.yaml b/.github/workflows/build_uv.yaml index 4fd9134..0b03860 100644 --- a/.github/workflows/build_uv.yaml +++ b/.github/workflows/build_uv.yaml @@ -13,7 +13,7 @@ jobs: strategy: matrix: platform: [ ubuntu-latest, macos-latest ] - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ${{ matrix.platform }} steps: From c9ecd6595d34bb727a8102d49327decd04d5e3b9 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 31 Jan 2026 21:17:47 +0000 Subject: [PATCH 11/72] doc: added uv commands to installation doc --- docs/source/installation.rst | 121 ++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 32 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index c73e3ce..89cb516 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -33,7 +33,7 @@ First install `pyproximal` with your package manager of choice. .. code-block:: bash - conda install --channel conda-forge pyproximal + >> conda install --channel conda-forge pyproximal Most of the dependencies (all required and some of the optional) are automatically installed for you. @@ -42,27 +42,27 @@ First install `pyproximal` with your package manager of choice. .. code-block:: bash - pip install pyproximal + >> pip install pyproximal Only the *required* dependencies are installed. To install some of the optional dependencies, run: .. code-block:: bash - pip install "pyproximal[advanced]" + >> pip install "pyproximal[advanced]" .. tab-item:: :iconify:`material-icon-theme:uv` uv .. code-block:: bash - uv add pyproximal + >> uv add pyproximal Only the *required* dependencies are installed. To install some of the optional dependencies, run: .. code-block:: bash - uv add "pyproximal[advanced]" + >> uv add "pyproximal[advanced]" From Source @@ -75,13 +75,13 @@ To access the latest source from github: .. code-block:: bash - pip install https://github.com/PyLops/pyproximal.git@dev + >> pip install https://github.com/PyLops/pyproximal.git@dev .. tab-item:: :iconify:`material-icon-theme:uv` uv .. code-block:: bash - uv add git+https://github.com/PyLops/pyproximal.git --branch dev + >> uv add git+https://github.com/PyLops/pyproximal.git --branch dev .. _DevInstall: @@ -103,35 +103,53 @@ Install dependencies We recommend installing dependencies into a separate environment. For that end, we provide a `Makefile` with useful commands for setting up the environment. -Conda (recommended) -------------------- -For a ``conda`` environment, run +.. tab-set:: -.. code-block:: bash + .. tab-item:: :iconify:`devicon:anaconda` conda - >> make dev-install_conda # for x86 (Intel or AMD CPUs) - >> make dev-install_conda_arm # for arm (M-series Mac) + .. code-block:: bash -This will create and activate an environment called ``pylops``, with all required and optional dependencies. + >> make dev-install_conda # for x86 (Intel or AMD CPUs) + >> make dev-install_conda_arm # for arm (M-series Mac) + + This creates and activate an environment called ``pyproximal``, with + all required and optional dependencies. + + .. tab-item:: :iconify:`devicon:pypi` pip -Pip ---- -If you prefer a ``pip`` installation, we provide the following command + .. code-block:: bash -.. code-block:: bash + >> make dev-install + + Thid does not create a virtual environment. Make sure you create and + activate your environment before running this command. - >> make dev-install + .. tab-item:: :iconify:`material-icon-theme:uv` uv -Note that, differently from the ``conda`` command, the above **will not** create a virtual environment. -Make sure you create and activate your environment previously. + .. code-block:: bash + + >> uv sync + + This also creates a virtual environment `.venv` that can be activated at + any time with `source .venv/bin/activate` (Linux/macOS). Run tests ========= To ensure that everything has been setup correctly, run tests: -.. code-block:: bash +.. tab-set:: + + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + + .. code-block:: bash + + >> make tests + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash - >> make tests + >> make tests_uv Make sure no tests fail, this guarantees that the installation has been successful. @@ -151,7 +169,6 @@ From then on, you can pull changes (for example, in the dev branch) with: >> git pull upstream dev - Install pre-commit hooks ======================== To ensure consistency in the coding style of our developers we rely on @@ -161,9 +178,19 @@ that have been configured in the ``.pre-commit-config.yaml`` file. In order to setup such hooks in your local repository, run: -.. code-block:: bash +.. tab-set:: + + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + + .. code-block:: bash + + >> pre-commit install + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash - >> pre-commit install + >> uv run pre-commit install Once this is set up, when committing changes, ``pre-commit`` will reject and "fix" your code by running the proper hooks. At this point, the user must check the changes and then stage them before trying to commit again. @@ -173,20 +200,50 @@ Final steps PyLops does not enforce the use of a linter as a pre-commit hook, but we do highly encourage using one before submitting a Pull Request. A properly configured linter (``flake8``) can be run with: -.. code-block:: bash +.. tab-set:: + + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip - >> make lint + .. code-block:: bash + + >> make lint + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash + + >> make lint_uv In addition, it is highly encouraged to build the docs prior to submitting a Pull Request. Apart from ensuring that docstrings are properly formatted, they can aid in catching bugs during development. Build (or update) the docs with: -.. code-block:: bash +.. tab-set:: + + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip - >> make doc + .. code-block:: bash + + >> make doc + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash + + >> make doc_uv or -.. code-block:: bash +.. tab-set:: + + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + + .. code-block:: bash + + >> make docupdate + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash - >> make docupdate + >> make docupdate_uv From a30f409f515a0e06d06d9c6120a1ad437138b828 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 31 Jan 2026 21:28:10 +0000 Subject: [PATCH 12/72] doc: move rtd to use uv --- .readthedocs.yaml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 19c37c3..1adbefe 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,19 +5,21 @@ # Required version: 2 -# Set the version of Python and other tools you might need -build: - os: ubuntu-20.04 - tools: - python: "3.11" - # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py -# Declare the Python requirements required to build your docs -python: - install: - - requirements: requirements-doc.txt - - method: pip - path: . +# Set the version of environment using UV +build: + os: ubuntu-24.04 + tools: + python: "3.13" + jobs: + pre_create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + create_environment: + - uv venv "${READTHEDOCS_VIRTUALENV_PATH}" + install: + - UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv sync --locked --all-extras --all-groups From aec8287af55b023598d18996191a422a4db79602 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 15:33:29 +0000 Subject: [PATCH 13/72] build: update uv.lock with simplified generation --- Makefile | 5 +++++ uv.lock | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ad58435..892e6fa 100755 --- a/Makefile +++ b/Makefile @@ -66,8 +66,13 @@ docupdate_uv: cd docs && $(UV) run make html && cd .. servedoc: + make pythoncheck $(PYTHON) -m http.server --directory docs/build/html/ +servedoc_uv: + make uvcheck + $(UV) run python -m http.server --directory docs/build/html/ + lint: flake8 docs/source examples/ pyproximal/ pytests/ tutorials/ diff --git a/uv.lock b/uv.lock index 508f2e3..e40423c 100644 --- a/uv.lock +++ b/uv.lock @@ -52,11 +52,11 @@ wheels = [ [[package]] name = "babel" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] From 47676e27ce330fce9d1d2217c7b5c3555b1f36b5 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 15:54:17 +0000 Subject: [PATCH 14/72] build: finalized uv.lock --- pyproject.toml | 4 ++-- uv.lock | 20 ++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 11bc6e8..a8571b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,8 +57,8 @@ dev = [ doc = [ "matplotlib", "scikit-image", - "bm3d<4.2.4", - "bm4d<4.2.4", + "bm3d", + "bm4d", "pooch", "sphinx", "sphinx-design", diff --git a/uv.lock b/uv.lock index e40423c..45f6d28 100644 --- a/uv.lock +++ b/uv.lock @@ -135,23 +135,19 @@ css = [ [[package]] name = "bm3d" -version = "4.0.1" +version = "4.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bm4d" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/0f/5387ec8e07b3345e9b8e23cba2a28e27a6b7bccb3e74e2e63dd23a8ca8b5/bm3d-4.0.1.tar.gz", hash = "sha256:92ad34a0923facd2d5211700093c7fff196133f28cf646244b0598a7daf90dd3", size = 1068761, upload-time = "2022-12-20T02:21:31.328Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/3a/ef3cbc420270472e3b4302acfd259ed58f5508ac217cc23eb9d1c7c1b204/bm3d-4.0.3.tar.gz", hash = "sha256:e9cda8d65134b384fe1af50a40a446a77774711c1a3a7412e2144f5e8f3e4088", size = 1069197, upload-time = "2024-09-06T15:41:34.304Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/ff/ab6cb80798cde8cc14f1970f0a244494d0bdcb0d5371c61d030a7c55a7c5/bm3d-4.0.1-py3-none-any.whl", hash = "sha256:81e430d4a7aa5f30e51f8c61eab55265e3e074a4f5cea67ab0238eb4bf15cc7f", size = 9979, upload-time = "2022-12-20T02:21:17.289Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2b/7f0990efc53f85dd90213b33a6ac0356d5adbb52e0c5bf15a66e9b5193ef/bm3d-4.0.3-py3-none-any.whl", hash = "sha256:fc4dfc0de0cd810fcb6ad198e1d0c6f99cf19d41f2ec69ff867674cfb9f2a775", size = 10148, upload-time = "2024-09-06T15:41:32.02Z" }, ] [[package]] name = "bm4d" -version = "4.2.3" +version = "4.2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -161,9 +157,9 @@ dependencies = [ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/c6/6b46e32625b9bf36bfcd4a1b7fd6ca6ecaff7d32034273b15e056f43d3ee/bm4d-4.2.3.tar.gz", hash = "sha256:e4cd0865f304f584118b9e8af880f0f2212ae9c5aa5192cf5d96c40007659de6", size = 4095146, upload-time = "2022-12-20T02:21:27.247Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/33/d85ecd508ad6d0782002c21264d5d67f43d430d9e2304841bd5c7fb25753/bm4d-4.2.5.tar.gz", hash = "sha256:5a7ca4d85ecb570e8eb20213d035fc801da58227772fd077b0a8798f5342c9b4", size = 3891186, upload-time = "2024-09-06T15:40:13.357Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/ab/59a1f5ea27bfb0f9ba306cd109a0f3b763e696530cc00a1ce52a1704de21/bm4d-4.2.3-py3-none-any.whl", hash = "sha256:752c3a5952624d760e5251ba38dd4e7e46b6e450deb7fc3bd05544128ac6c27a", size = 1068070, upload-time = "2022-12-20T02:21:15.4Z" }, + { url = "https://files.pythonhosted.org/packages/19/47/034be51bc2b40279fd1417397d3f330b73906774bd6eaecd26ed93c1491f/bm4d-4.2.5-py3-none-any.whl", hash = "sha256:601338bbd54bcbd971d70b5a6961583a9ccaa564c982389cf3f82bd0a6d5bad5", size = 862033, upload-time = "2024-09-06T15:40:10.58Z" }, ] [[package]] @@ -1948,8 +1944,8 @@ dev = [ { name = "pytest" }, ] doc = [ - { name = "bm3d", specifier = "<4.2.4" }, - { name = "bm4d", specifier = "<4.2.4" }, + { name = "bm3d" }, + { name = "bm4d" }, { name = "image" }, { name = "matplotlib" }, { name = "nbsphinx" }, From 24526c43807ab9d707f9372b4d2bb3c9f14b01a1 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 19:42:15 +0000 Subject: [PATCH 15/72] build: migrate content of setup.cfg to pyproject.toml --- .pre-commit-config.yaml | 5 +++++ environment-dev-arm.yml | 1 + environment-dev.yml | 1 + pyproject.toml | 12 ++++++++++++ requirements-dev.txt | 9 +++++---- requirements-doc.txt | 9 +++++---- setup.cfg | 12 ------------ uv.lock | 14 ++++++++++++++ 8 files changed, 43 insertions(+), 20 deletions(-) delete mode 100755 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c45b1d..3c5ce3f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,3 +28,8 @@ repos: "--filter-files", "--line-length=88", ] + + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.9.28 + hooks: + - id: uv-lock diff --git a/environment-dev-arm.yml b/environment-dev-arm.yml index d99d889..acbeb35 100644 --- a/environment-dev-arm.yml +++ b/environment-dev-arm.yml @@ -34,4 +34,5 @@ dependencies: - sphinxemoji - image - flake8 + - flake8-pyproject - mypy diff --git a/environment-dev.yml b/environment-dev.yml index 189f04f..a9d538f 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -35,4 +35,5 @@ dependencies: - sphinxemoji - image - flake8 + - flake8-pyproject - mypy diff --git a/pyproject.toml b/pyproject.toml index a8571b1..6fd27cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ dev = [ "pytest", "isort", "flake8", + "flake8-pyproject", "pre-commit", "autopep8", "black", @@ -77,6 +78,17 @@ exclude = ["pytests"] [tool.setuptools_scm] version_file = "pyproximal/version.py" +[tool.pytest.ini_options] +addopts = "--verbose" +python_files = ["pytests/*.py"] + +[tool.flake8] +ignore = ["E203", "E501", "W503", "E402"] +max-line-length = 88 +per-file-ignores = [ + "__init__.py:F401,F403,F405" +] + [tool.mypy] files = ["pyproximal", ] python_version = "3.10" diff --git a/requirements-dev.txt b/requirements-dev.txt index 4f5d744..6db65e2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,20 +5,20 @@ numba scikit-image matplotlib ipython -bm4d<4.2.4 # temporary as gclib problem arises in readthedocs -bm3d<4.0.2 # temporary as gclib problem arises in readthedocs +bm4d +bm3d typing_extensions pytest pytest-runner setuptools_scm -docutils<0.18 -Sphinx +sphinx pooch shibuya sphinx-design sphinx-gallery sphinx-iconify sphinxemoji +shibuya numpydoc nbsphinx image @@ -27,4 +27,5 @@ autopep8 isort black flake8 +flake8-pyproject mypy diff --git a/requirements-doc.txt b/requirements-doc.txt index 4f5d744..6db65e2 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -5,20 +5,20 @@ numba scikit-image matplotlib ipython -bm4d<4.2.4 # temporary as gclib problem arises in readthedocs -bm3d<4.0.2 # temporary as gclib problem arises in readthedocs +bm4d +bm3d typing_extensions pytest pytest-runner setuptools_scm -docutils<0.18 -Sphinx +sphinx pooch shibuya sphinx-design sphinx-gallery sphinx-iconify sphinxemoji +shibuya numpydoc nbsphinx image @@ -27,4 +27,5 @@ autopep8 isort black flake8 +flake8-pyproject mypy diff --git a/setup.cfg b/setup.cfg deleted file mode 100755 index aeb0c97..0000000 --- a/setup.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[aliases] -test=pytest - -[tool:pytest] -addopts = --verbose -python_files = pytests/*.py - -[flake8] -ignore = E203, E501, W503, E402 -per-file-ignores = - __init__.py: F401, F403, F405 -max-line-length = 88 diff --git a/uv.lock b/uv.lock index 45f6d28..084e4e3 100644 --- a/uv.lock +++ b/uv.lock @@ -659,6 +659,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, ] +[[package]] +name = "flake8-pyproject" +version = "1.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/6a/cdee9ff7f2b7c6ddc219fd95b7c70c0a3d9f0367a506e9793eedfc72e337/flake8_pyproject-1.2.4-py3-none-any.whl", hash = "sha256:ea34c057f9a9329c76d98723bb2bb498cc6ba8ff9872c4d19932d48c91249a77", size = 5694, upload-time = "2025-11-28T21:40:01.309Z" }, +] + [[package]] name = "fonttools" version = "4.61.1" @@ -1896,6 +1908,7 @@ dev = [ { name = "autopep8" }, { name = "black" }, { name = "flake8" }, + { name = "flake8-pyproject" }, { name = "isort" }, { name = "mypy" }, { name = "pre-commit" }, @@ -1938,6 +1951,7 @@ dev = [ { name = "autopep8" }, { name = "black" }, { name = "flake8" }, + { name = "flake8-pyproject" }, { name = "isort" }, { name = "mypy" }, { name = "pre-commit" }, From 0fd5b50dee142890856d0a744087a17ec0d2112e Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:04:06 +0000 Subject: [PATCH 16/72] build: switch to using ruff --- .github/workflows/build.yaml | 34 +++--- .github/workflows/build_uv.yaml | 32 ------ .github/workflows/ruff.yaml | 25 +++++ .pre-commit-config.yaml | 32 +++--- Makefile | 4 +- environment-dev-arm.yml | 5 +- environment-dev.yml | 5 +- pyproject.toml | 25 +++-- requirements-dev.txt | 5 +- requirements-doc.txt | 31 ------ uv.lock | 184 +++++--------------------------- 11 files changed, 99 insertions(+), 283 deletions(-) delete mode 100644 .github/workflows/build_uv.yaml create mode 100644 .github/workflows/ruff.yaml delete mode 100644 requirements-doc.txt diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8021aee..e9a4c21 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,24 +17,16 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 - - name: Get history and tags for SCM versioning to work - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements-dev.txt; fi - - name: Install pyproximal - run: | - python -m setuptools_scm - pip install . - - name: Test with pytest - run: | - pytest + - uses: actions/checkout@v4 + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Install uv with Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and pyproximal + run: uv sync --locked --all-extras --all-groups + - name: Test with pytest + run: uv run pytest --color=yes pytests/ diff --git a/.github/workflows/build_uv.yaml b/.github/workflows/build_uv.yaml deleted file mode 100644 index 0b03860..0000000 --- a/.github/workflows/build_uv.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: PyProximal Testing with UV - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - - main - - dev - -jobs: - build: - strategy: - matrix: - platform: [ ubuntu-latest, macos-latest ] - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] - - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v4 - - name: Get history and tags for SCM versioning to work - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: Install uv with Python ${{ matrix.python-version }} - uses: astral-sh/setup-uv@v6 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies and pyproximal - run: uv sync --locked --all-extras --all-groups - - name: Test with pytest - run: uv run pytest --color=yes pytests/ diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml new file mode 100644 index 0000000..4a8d71b --- /dev/null +++ b/.github/workflows/ruff.yaml @@ -0,0 +1,25 @@ +# This workflow runs Ruff on the PR +# For more information see: https://github.com/marketplace/actions/ruff-action +name: PyProximal-ruff + +on: [push, pull_request] + +jobs: + ruff-lint: + runs-on: ubuntu-latest + name: Lint + steps: + - name: Check out source repository + uses: actions/checkout@v4 + - name: Set up Python environment + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: ruff Lint + uses: astral-sh/ruff-action@v3 + with: + args: > + check pyproximal + --line-length 88 + --ignore E203,E501,W503,E402 + --per-file-ignores "__init__.py:F401,F403,F405" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3c5ce3f..993ceeb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,29 +7,21 @@ repos: - id: end-of-file-fixer - id: check-yaml - - repo: https://github.com/psf/black - rev: 25.1.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.14.14 hooks: - - id: black - args: # arguments to configure black - - --line-length=88 - - - repo: https://github.com/pycqa/isort - rev: 6.0.1 - hooks: - - id: isort - name: isort (python) - args: - [ - "--profile", - "black", - "--skip", - "__init__.py", - "--filter-files", - "--line-length=88", - ] + - id: ruff-check + args: [ --fix ] + - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit rev: 0.9.28 hooks: - id: uv-lock + + - repo: https://github.com/abravalheri/validate-pyproject + rev: "v0.23" + hooks: + - id: validate-pyproject + additional_dependencies: ["validate-pyproject-schema-store[all]"] diff --git a/Makefile b/Makefile index 892e6fa..fd47813 100755 --- a/Makefile +++ b/Makefile @@ -74,11 +74,11 @@ servedoc_uv: $(UV) run python -m http.server --directory docs/build/html/ lint: - flake8 docs/source examples/ pyproximal/ pytests/ tutorials/ + ruff check docs/source examples/ pyproximal/ pytests/ tutorials/ lint_uv: make uvcheck - $(UV) run flake8 docs/source examples/ pyproximal/ pytests/ tutorials/ + $(UV) run ruff check docs/source examples/ pyproximal/ pytests/ tutorials/ typeannot: mypy pyproximal/ diff --git a/environment-dev-arm.yml b/environment-dev-arm.yml index acbeb35..4e3a76d 100644 --- a/environment-dev-arm.yml +++ b/environment-dev-arm.yml @@ -18,8 +18,6 @@ dependencies: - numba - pre-commit - autopep8 - - isort - - black - pip: - bm4d - bm3d @@ -33,6 +31,5 @@ dependencies: - nbsphinx - sphinxemoji - image - - flake8 - - flake8-pyproject + - ruff - mypy diff --git a/environment-dev.yml b/environment-dev.yml index a9d538f..2077e34 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -19,8 +19,6 @@ dependencies: - icc_rt - pre-commit - autopep8 - - isort - - black - pip: - bm4d - bm3d @@ -34,6 +32,5 @@ dependencies: - nbsphinx - sphinxemoji - image - - flake8 - - flake8-pyproject + - ruff - mypy diff --git a/pyproject.toml b/pyproject.toml index 6fd27cd..178e89b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,12 +47,9 @@ advanced = [ [dependency-groups] dev = [ "pytest", - "isort", - "flake8", - "flake8-pyproject", "pre-commit", "autopep8", - "black", + "ruff", "mypy", ] doc = [ @@ -82,11 +79,21 @@ version_file = "pyproximal/version.py" addopts = "--verbose" python_files = ["pytests/*.py"] -[tool.flake8] -ignore = ["E203", "E501", "W503", "E402"] -max-line-length = 88 -per-file-ignores = [ - "__init__.py:F401,F403,F405" +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +ignore = [ + #"E203", # whitespace before ':', conflicts with black + "E501", # line length handled by formatter + "E402", # module level import not at top of file +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = [ + "F401", # imported but unused (re-exports) + "F403", # star import used for public API + "F405", # name may be undefined from star import ] [tool.mypy] diff --git a/requirements-dev.txt b/requirements-dev.txt index 6db65e2..a0b94ca 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,8 +24,5 @@ nbsphinx image pre-commit autopep8 -isort -black -flake8 -flake8-pyproject +ruff mypy diff --git a/requirements-doc.txt b/requirements-doc.txt deleted file mode 100644 index 6db65e2..0000000 --- a/requirements-doc.txt +++ /dev/null @@ -1,31 +0,0 @@ -numpy>=1.21.0 -scipy>=1.11.0 -pylops>=2.0.0 -numba -scikit-image -matplotlib -ipython -bm4d -bm3d -typing_extensions -pytest -pytest-runner -setuptools_scm -sphinx -pooch -shibuya -sphinx-design -sphinx-gallery -sphinx-iconify -sphinxemoji -shibuya -numpydoc -nbsphinx -image -pre-commit -autopep8 -isort -black -flake8 -flake8-pyproject -mypy diff --git a/uv.lock b/uv.lock index 084e4e3..26a799d 100644 --- a/uv.lock +++ b/uv.lock @@ -72,50 +72,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] -[[package]] -name = "black" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pytokens" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/1b/523329e713f965ad0ea2b7a047eeb003007792a0353622ac7a8cb2ee6fef/black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168", size = 1849661, upload-time = "2026-01-18T04:59:12.425Z" }, - { url = "https://files.pythonhosted.org/packages/14/82/94c0640f7285fa71c2f32879f23e609dd2aa39ba2641f395487f24a578e7/black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d", size = 1689065, upload-time = "2026-01-18T04:59:13.993Z" }, - { url = "https://files.pythonhosted.org/packages/f0/78/474373cbd798f9291ed8f7107056e343fd39fef42de4a51c7fd0d360840c/black-26.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0", size = 1751502, upload-time = "2026-01-18T04:59:15.971Z" }, - { url = "https://files.pythonhosted.org/packages/29/89/59d0e350123f97bc32c27c4d79563432d7f3530dca2bff64d855c178af8b/black-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24", size = 1400102, upload-time = "2026-01-18T04:59:17.8Z" }, - { url = "https://files.pythonhosted.org/packages/e1/bc/5d866c7ae1c9d67d308f83af5462ca7046760158bbf142502bad8f22b3a1/black-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89", size = 1207038, upload-time = "2026-01-18T04:59:19.543Z" }, - { url = "https://files.pythonhosted.org/packages/30/83/f05f22ff13756e1a8ce7891db517dbc06200796a16326258268f4658a745/black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5", size = 1831956, upload-time = "2026-01-18T04:59:21.38Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/b2c570550e39bedc157715e43927360312d6dd677eed2cc149a802577491/black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68", size = 1672499, upload-time = "2026-01-18T04:59:23.257Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d7/990d6a94dc9e169f61374b1c3d4f4dd3037e93c2cc12b6f3b12bc663aa7b/black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14", size = 1735431, upload-time = "2026-01-18T04:59:24.729Z" }, - { url = "https://files.pythonhosted.org/packages/36/1c/cbd7bae7dd3cb315dfe6eeca802bb56662cc92b89af272e014d98c1f2286/black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c", size = 1400468, upload-time = "2026-01-18T04:59:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/59/b1/9fe6132bb2d0d1f7094613320b56297a108ae19ecf3041d9678aec381b37/black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4", size = 1207332, upload-time = "2026-01-18T04:59:28.711Z" }, - { url = "https://files.pythonhosted.org/packages/f5/13/710298938a61f0f54cdb4d1c0baeb672c01ff0358712eddaf29f76d32a0b/black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f", size = 1878189, upload-time = "2026-01-18T04:59:30.682Z" }, - { url = "https://files.pythonhosted.org/packages/79/a6/5179beaa57e5dbd2ec9f1c64016214057b4265647c62125aa6aeffb05392/black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6", size = 1700178, upload-time = "2026-01-18T04:59:32.387Z" }, - { url = "https://files.pythonhosted.org/packages/8c/04/c96f79d7b93e8f09d9298b333ca0d31cd9b2ee6c46c274fd0f531de9dc61/black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a", size = 1777029, upload-time = "2026-01-18T04:59:33.767Z" }, - { url = "https://files.pythonhosted.org/packages/49/f9/71c161c4c7aa18bdda3776b66ac2dc07aed62053c7c0ff8bbda8c2624fe2/black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791", size = 1406466, upload-time = "2026-01-18T04:59:35.177Z" }, - { url = "https://files.pythonhosted.org/packages/4a/8b/a7b0f974e473b159d0ac1b6bcefffeb6bec465898a516ee5cc989503cbc7/black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954", size = 1216393, upload-time = "2026-01-18T04:59:37.18Z" }, - { url = "https://files.pythonhosted.org/packages/79/04/fa2f4784f7237279332aa735cdfd5ae2e7730db0072fb2041dadda9ae551/black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304", size = 1877781, upload-time = "2026-01-18T04:59:39.054Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ad/5a131b01acc0e5336740a039628c0ab69d60cf09a2c87a4ec49f5826acda/black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9", size = 1699670, upload-time = "2026-01-18T04:59:41.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/7c/b05f22964316a52ab6b4265bcd52c0ad2c30d7ca6bd3d0637e438fc32d6e/black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b", size = 1775212, upload-time = "2026-01-18T04:59:42.545Z" }, - { url = "https://files.pythonhosted.org/packages/a6/a3/e8d1526bea0446e040193185353920a9506eab60a7d8beb062029129c7d2/black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b", size = 1409953, upload-time = "2026-01-18T04:59:44.357Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5a/d62ebf4d8f5e3a1daa54adaab94c107b57be1b1a2f115a0249b41931e188/black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca", size = 1217707, upload-time = "2026-01-18T04:59:45.719Z" }, - { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, - { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, - { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, - { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, -] - [[package]] name = "bleach" version = "6.3.0" @@ -351,18 +307,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] -[[package]] -name = "click" -version = "8.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -645,32 +589,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] -[[package]] -name = "flake8" -version = "7.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, -] - -[[package]] -name = "flake8-pyproject" -version = "1.2.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flake8" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/6a/cdee9ff7f2b7c6ddc219fd95b7c70c0a3d9f0367a506e9793eedfc72e337/flake8_pyproject-1.2.4-py3-none-any.whl", hash = "sha256:ea34c057f9a9329c76d98723bb2bb498cc6ba8ff9872c4d19932d48c91249a77", size = 5694, upload-time = "2025-11-28T21:40:01.309Z" }, -] - [[package]] name = "fonttools" version = "4.61.1" @@ -790,15 +708,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "isort" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, -] - [[package]] name = "jinja2" version = "3.1.6" @@ -1257,15 +1166,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "mistune" version = "3.2.0" @@ -1831,15 +1731,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] -[[package]] -name = "pyflakes" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -1906,13 +1797,10 @@ advanced = [ [package.dev-dependencies] dev = [ { name = "autopep8" }, - { name = "black" }, - { name = "flake8" }, - { name = "flake8-pyproject" }, - { name = "isort" }, { name = "mypy" }, { name = "pre-commit" }, { name = "pytest" }, + { name = "ruff" }, ] doc = [ { name = "bm3d" }, @@ -1949,13 +1837,10 @@ provides-extras = ["advanced"] [package.metadata.requires-dev] dev = [ { name = "autopep8" }, - { name = "black" }, - { name = "flake8" }, - { name = "flake8-pyproject" }, - { name = "isort" }, { name = "mypy" }, { name = "pre-commit" }, { name = "pytest" }, + { name = "ruff" }, ] doc = [ { name = "bm3d" }, @@ -2004,45 +1889,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[package]] -name = "pytokens" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, - { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, - { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, - { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, - { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, - { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, - { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, - { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, - { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, - { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, - { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, - { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, - { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, - { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, - { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, - { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, -] - [[package]] name = "pywavelets" version = "1.8.0" @@ -2454,6 +2300,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] +[[package]] +name = "ruff" +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +] + [[package]] name = "scikit-image" version = "0.25.2" From 6cb36dc4dacdaad0999927fd9747782a1b2ca907 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:05:35 +0000 Subject: [PATCH 17/72] minor: fix ruff GA --- .github/workflows/ruff.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 4a8d71b..e23fb3e 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -21,5 +21,5 @@ jobs: args: > check pyproximal --line-length 88 - --ignore E203,E501,W503,E402 + --ignore E203,E501,E402 --per-file-ignores "__init__.py:F401,F403,F405" From 36a1cd8668af0074b5ed0d968d53ba4f5ce927b2 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:07:40 +0000 Subject: [PATCH 18/72] minor: one more fix of ruff GA --- .github/workflows/ruff.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index e23fb3e..c189e3e 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -22,4 +22,4 @@ jobs: check pyproximal --line-length 88 --ignore E203,E501,E402 - --per-file-ignores "__init__.py:F401,F403,F405" + --per-file-ignores "__init__.py:F401,__init__.py:F403,__init__.py:F405" From c2d485a7e8e77a56cc1ef17b149994d7d4df783c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:09:48 +0000 Subject: [PATCH 19/72] minor: simplify args in ruff GA --- .github/workflows/ruff.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index c189e3e..d064809 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -18,8 +18,4 @@ jobs: - name: ruff Lint uses: astral-sh/ruff-action@v3 with: - args: > - check pyproximal - --line-length 88 - --ignore E203,E501,E402 - --per-file-ignores "__init__.py:F401,__init__.py:F403,__init__.py:F405" + args: check pyproximal From 91d323af396c97ce532d2032d956cf910ebd0ce9 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:13:32 +0000 Subject: [PATCH 20/72] minor: added src to ruff in pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 178e89b..3a4a7f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ addopts = "--verbose" python_files = ["pytests/*.py"] [tool.ruff] +src = ["pyproximal"] line-length = 88 [tool.ruff.lint] From 53506672c04b45ad53a12b839fb572519472e2dd Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:16:48 +0000 Subject: [PATCH 21/72] minor: add src in ruff GA --- .github/workflows/ruff.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index d064809..f966a2d 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -18,4 +18,4 @@ jobs: - name: ruff Lint uses: astral-sh/ruff-action@v3 with: - args: check pyproximal + src: "./pyproximal" From fb68cb41b6a6b4284786d76d26205ea999442c06 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:17:58 +0000 Subject: [PATCH 22/72] minor: rm python install in ruff GA --- .github/workflows/ruff.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index f966a2d..c4ba642 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -11,10 +11,6 @@ jobs: steps: - name: Check out source repository uses: actions/checkout@v4 - - name: Set up Python environment - uses: actions/setup-python@v5 - with: - python-version: "3.11" - name: ruff Lint uses: astral-sh/ruff-action@v3 with: From aa651067712bb2b50afc2b77f12faf6831b7cc1e Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:18:41 +0000 Subject: [PATCH 23/72] minor: remove flake8 GA --- .github/workflows/flake8.yaml | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .github/workflows/flake8.yaml diff --git a/.github/workflows/flake8.yaml b/.github/workflows/flake8.yaml deleted file mode 100644 index 05905e1..0000000 --- a/.github/workflows/flake8.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# This workflow runs Flake8 on the PR -# For more information see: https://github.com/marketplace/actions/python-flake8-lint -name: PyProximal-flake8 - -on: [push, pull_request] - -jobs: - flake8-lint: - runs-on: ubuntu-latest - name: Lint - steps: - - name: Check out source repository - uses: actions/checkout@v4 - - name: Set up Python environment - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: flake8 Lint - uses: py-actions/flake8@v2 - with: - ignore: "E203,E501,W503,E402" - max-line-length: "88" - path: "pyproximal" - args: "--per-file-ignores=__init__.py:F401,F403,F405" From 744e4baf0be0c5497aed256529755e260ca8fa10 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:35:42 +0000 Subject: [PATCH 24/72] doc: switch from flake8 to ruff in doc --- CONTRIBUTING.md | 15 ++++++++--- docs/source/contributing.rst | 49 ++++++++++++++++++++++++++++-------- docs/source/installation.rst | 2 +- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c81f550..3564b4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,14 +58,21 @@ Ready to contribute? ``` make tests ``` + or + ``` + make tests_uv + ``` -4. Run flake8 to check the quality of your code: +4. Run ruff to check the quality of your code: ``` make lint ``` - Note that PyLops does not enforce full compliance with flake8, rather this is used as a -guideline and will also be run as part of our CI. -Make sure to limit to a minimum flake8 warnings before making a PR. + or + ``` + make lint_uv + ``` + + Note that PyProximal enforces full compliance with ruff and it will also be run as part of our CI. 5. Update the docs ``` diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index bb4f911..7131473 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -65,26 +65,55 @@ Now you can make your changes locally. 3. When you're done making changes, check that your code follows the guidelines for :ref:`addingoperator` and that both old and new tests pass successfully: -.. code-block:: bash +.. tab-set:: - >> make tests + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip -4. Run flake8 to check the quality of your code: + .. code-block:: bash -.. code-block:: bash + >> make tests + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash + + >> make tests_uv + +4. Run ruff to check the quality of your code: + + +.. tab-set:: - >> make lint + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip -Note that PyProximal does not enforce full compliance with flake8, rather this is used as a -guideline and will also be run as part of our CI. -Make sure to limit to a minimum flake8 warnings before making a PR. + .. code-block:: bash + + >> make lint + + .. tab-item:: :iconify:`material-icon-theme:uv` uv + + .. code-block:: bash + + >> make lint_uv + +Note that PyProximal enforces full compliance with ruff and it +will also be run as part of our CI. 5. Update the docs -.. code-block:: bash +.. tab-set:: + + .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + + .. code-block:: bash + + >> make docupdate + + .. tab-item:: :iconify:`material-icon-theme:uv` uv - >> make docupdate + .. code-block:: bash + >> make docupdate_uv 6. Commit your changes and push your branch to GitLab: diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 89cb516..9fcc9a4 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -198,7 +198,7 @@ At this point, the user must check the changes and then stage them before trying Final steps =========== PyLops does not enforce the use of a linter as a pre-commit hook, but we do highly encourage using one before submitting a Pull Request. -A properly configured linter (``flake8``) can be run with: +A properly configured linter (``ruff``) can be run with: .. tab-set:: From e3675ff46e1a712ca0d0d0d279eaabededb7399c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:58:01 +0000 Subject: [PATCH 25/72] minor: improve ruff ignores --- .pre-commit-config.yaml | 6 ++++++ pyproject.toml | 11 ++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 993ceeb..9def2cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,13 @@ repos: hooks: - id: trailing-whitespace - id: end-of-file-fixer + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: check-toml - id: check-yaml + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. diff --git a/pyproject.toml b/pyproject.toml index 3a4a7f2..6941a1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,19 +83,16 @@ python_files = ["pytests/*.py"] src = ["pyproximal"] line-length = 88 -[tool.ruff.lint] -ignore = [ - #"E203", # whitespace before ':', conflicts with black - "E501", # line length handled by formatter - "E402", # module level import not at top of file -] - [tool.ruff.lint.per-file-ignores] "__init__.py" = [ "F401", # imported but unused (re-exports) "F403", # star import used for public API "F405", # name may be undefined from star import ] +"tutorials/*.py" = [ + "E402", # module level import not at top of file + "E731", # do not assign a lambda expression, use def +] [tool.mypy] files = ["pyproximal", ] From bf57a4410caf03f6a1f161ee65eb6c22607074bd Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 1 Feb 2026 23:59:02 +0000 Subject: [PATCH 26/72] minor: change name of test GA --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e9a4c21..4dd6174 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,4 +1,4 @@ -name: PyProximal Testing +name: PyProximal-testing on: pull_request: From ea9defb5993f6ecc7de990a97575a86f07d33b4a Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 2 Feb 2026 00:01:25 +0000 Subject: [PATCH 27/72] minor: change name of deploy GA --- .github/workflows/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 3e2aca6..fb48608 100755 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,6 +1,6 @@ # This workflow uploads PyProx on PyPI using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries -name: PyProx-deploy +name: PyProximal-deploy on: release: From 25c0e6e120c88798192d8c80ef4500ad70a4f1b0 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 2 Feb 2026 19:33:16 +0000 Subject: [PATCH 28/72] minor: resolve all mypy errors --- pyproject.toml | 3 ++- pyproximal/proximal/Simplex.py | 2 +- pyproximal/proximal/_Simplex_numba.py | 31 +++++++++++++++++++++++---- pyproximal/utils/utils.py | 4 ++-- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6941a1e..c94a839 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,10 +96,11 @@ line-length = 88 [tool.mypy] files = ["pyproximal", ] -python_version = "3.10" +python_version = "3.11" mypy_path = "pyproximal" strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +disable_error_code = ["untyped-decorator"] warn_unreachable = true allow_redefinition = true disallow_untyped_defs = true diff --git a/pyproximal/proximal/Simplex.py b/pyproximal/proximal/Simplex.py index 1542cab..60809cd 100644 --- a/pyproximal/proximal/Simplex.py +++ b/pyproximal/proximal/Simplex.py @@ -11,7 +11,7 @@ try: from numba import jit - from ._Simplex_cuda import simplex_jit_cuda + from ._Simplex_cuda import simplex_jit_cuda # type: ignore[attr-defined] from ._Simplex_numba import bisect_jit, fun_jit, simplex_jit except ModuleNotFoundError: jit = None diff --git a/pyproximal/proximal/_Simplex_numba.py b/pyproximal/proximal/_Simplex_numba.py index 6e4c8fc..26ecfbf 100644 --- a/pyproximal/proximal/_Simplex_numba.py +++ b/pyproximal/proximal/_Simplex_numba.py @@ -1,15 +1,20 @@ +from typing import Any import os import numpy as np from numba import jit +from pylops.utils.typing import NDArray + # detect whether to use parallel or not numba_threads = int(os.getenv("NUMBA_NUM_THREADS", "1")) parallel = True if numba_threads != 1 else False @jit(nopython=True) -def fun_jit(mu, x, coeffs, scalar, lower, upper): +def fun_jit( + mu: int, x: NDArray, coeffs: NDArray, scalar: float, lower: float, upper: float +) -> Any: """Bisection function""" return ( np.dot(coeffs, np.minimum(np.maximum(x - mu * coeffs, lower), upper)) - scalar @@ -18,8 +23,17 @@ def fun_jit(mu, x, coeffs, scalar, lower, upper): @jit(nopython=True, nogil=True) def bisect_jit( - x, coeffs, scalar, lower, upper, bisect_lower, bisect_upper, maxiter, ftol, xtol -): + x: NDArray, + coeffs: NDArray, + scalar: float, + lower: float, + upper: float, + bisect_lower: float, + bisect_upper: float, + maxiter: int, + ftol: float, + xtol: float, +) -> Any: """Bisection method Parameters @@ -64,7 +78,16 @@ def bisect_jit( @jit(nopython=True, parallel=parallel, nogil=True) -def simplex_jit(x, coeffs, scalar, lower, upper, maxiter, ftol, xtol): +def simplex_jit( + x: NDArray, + coeffs: NDArray, + scalar: float, + lower: float, + upper: float, + maxiter: int, + ftol: float, + xtol: float, +) -> NDArray: """Simplex proximal Parameters diff --git a/pyproximal/utils/utils.py b/pyproximal/utils/utils.py index 26a3e88..b060f01 100644 --- a/pyproximal/utils/utils.py +++ b/pyproximal/utils/utils.py @@ -1,10 +1,10 @@ __all__ = ["Report"] -# scooby is a soft dependency for pyproximal from types import ModuleType from typing import Optional try: + # scooby is a soft dependency for pyproximal from scooby import Report as ScoobyReport except ImportError: @@ -25,7 +25,7 @@ def __init__( ) -class Report(ScoobyReport): +class Report(ScoobyReport): # type: ignore[misc] r"""Print date, time, and version information. Use ``scooby`` to print date, time, and package version information in any From 826bf82c08f2fb4678c9827be85df0e375176ce4 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 2 Feb 2026 19:43:21 +0000 Subject: [PATCH 29/72] ci: added mypy GA to CI --- .github/workflows/mypy.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/mypy.yaml diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml new file mode 100644 index 0000000..1bc982f --- /dev/null +++ b/.github/workflows/mypy.yaml @@ -0,0 +1,21 @@ +# This workflow runs MyPy on the PR +name: PyProximal-mypy + +on: pull_request + +jobs: + mypy: + runs-on: ubuntu-latest + name: MyPy + steps: + - name: Check out source repository + uses: actions/checkout@v4 + - name: Install uv with Python + uses: astral-sh/setup-uv@v6 + with: + python-version: "3.11" + - name: Install dependencies and pyproximal + run: uv sync --locked --all-extras --all-groups + - name: Run Mypy + run: | + uv run mypy pyproximal/ From fe39aa3a278f141cd1289f33c4993600fe8a330a Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 6 Feb 2026 19:57:32 +0000 Subject: [PATCH 30/72] build: added support for coverage --- .github/workflows/mypy.yaml | 3 +- Makefile | 14 ++++- environment-dev-arm.yml | 1 + environment-dev.yml | 1 + pyproject.toml | 1 + requirements-dev.txt | 36 ++++++------- uv.lock | 101 ++++++++++++++++++++++++++++++++++++ 7 files changed, 137 insertions(+), 20 deletions(-) diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 1bc982f..08fbd56 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -1,7 +1,7 @@ # This workflow runs MyPy on the PR name: PyProximal-mypy -on: pull_request +on: [push, pull_request] jobs: mypy: @@ -14,6 +14,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: python-version: "3.11" + enable-cache: true - name: Install dependencies and pyproximal run: uv sync --locked --all-extras --all-groups - name: Run Mypy diff --git a/Makefile b/Makefile index fd47813..40c6106 100755 --- a/Makefile +++ b/Makefile @@ -2,7 +2,9 @@ PIP := $(shell command -v pip3 2> /dev/null || command which pip 2> /dev/null) PYTHON := $(shell command -v python3 2> /dev/null || command which python 2> /dev/null) UV := $(shell command -v uv 2> /dev/null || command which uv 2> /dev/null) -.PHONY: install dev-install install_conda dev-install_conda tests tests_uv doc doc_uv docupdate docupdate_uv servedoc lint lint_uv typeannot typeannot_uv +.PHONY: install dev-install install_conda dev-install_conda +.PHONY: tests tests_uv doc doc_uv docupdate docupdate_uv servedoc +.PHONY: lint lint_uv typeannot typeannot_uv coverage, coverage_uv pipcheck: ifndef PIP @@ -86,3 +88,13 @@ typeannot: typeannot_uv: make uvcheck $(UV) run mypy pyproximal/ + +coverage: + coverage run -m pytest && coverage xml && coverage html && $(PYTHON) -m http.server --directory htmlcov/ + +coverage_uv: + make uvcheck + $(UV) run coverage run -m pytest &&\ + $(UV) run coverage xml &&\ + $(UV) run coverage html &&\ + $(UV) run python -m http.server --directory htmlcov/ diff --git a/environment-dev-arm.yml b/environment-dev-arm.yml index 4e3a76d..e262e63 100644 --- a/environment-dev-arm.yml +++ b/environment-dev-arm.yml @@ -33,3 +33,4 @@ dependencies: - image - ruff - mypy + - coverage diff --git a/environment-dev.yml b/environment-dev.yml index 2077e34..8af0220 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -34,3 +34,4 @@ dependencies: - image - ruff - mypy + - coverage diff --git a/pyproject.toml b/pyproject.toml index c94a839..11505f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ dev = [ "autopep8", "ruff", "mypy", + "coverage" ] doc = [ "matplotlib", diff --git a/requirements-dev.txt b/requirements-dev.txt index a0b94ca..9b3f58a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,28 +1,28 @@ +autopep8 +bm3d +bm4d +coverage +image +ipython +matplotlib +mypy +nbsphinx +numba numpy>=1.21.0 -scipy>=1.11.0 +numpydoc +pooch +pre-commit pylops>=2.0.0 -numba -scikit-image -matplotlib -ipython -bm4d -bm3d -typing_extensions pytest pytest-runner +ruff +scikit-image +scipy>=1.11.0 setuptools_scm -sphinx -pooch shibuya +sphinx sphinx-design sphinx-gallery sphinx-iconify sphinxemoji -shibuya -numpydoc -nbsphinx -image -pre-commit -autopep8 -ruff -mypy +typing_extensions diff --git a/uv.lock b/uv.lock index 26a799d..a8f2384 100644 --- a/uv.lock +++ b/uv.lock @@ -472,6 +472,105 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] +[[package]] +name = "coverage" +version = "7.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/07/1c8099563a8a6c389a31c2d0aa1497cee86d6248bb4b9ba5e779215db9f9/coverage-7.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", size = 219143, upload-time = "2026-02-03T13:59:40.459Z" }, + { url = "https://files.pythonhosted.org/packages/69/39/a892d44af7aa092cab70e0cc5cdbba18eeccfe1d6930695dab1742eef9e9/coverage-7.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b", size = 219663, upload-time = "2026-02-03T13:59:41.951Z" }, + { url = "https://files.pythonhosted.org/packages/9a/25/9669dcf4c2bb4c3861469e6db20e52e8c11908cf53c14ec9b12e9fd4d602/coverage-7.13.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8", size = 246424, upload-time = "2026-02-03T13:59:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/68/d9766c4e298aca62ea5d9543e1dd1e4e1439d7284815244d8b7db1840bfb/coverage-7.13.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0", size = 248228, upload-time = "2026-02-03T13:59:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e2/eea6cb4a4bd443741adf008d4cccec83a1f75401df59b6559aca2bdd9710/coverage-7.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6", size = 250103, upload-time = "2026-02-03T13:59:46.271Z" }, + { url = "https://files.pythonhosted.org/packages/db/77/664280ecd666c2191610842177e2fab9e5dbdeef97178e2078fed46a3d2c/coverage-7.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f", size = 247107, upload-time = "2026-02-03T13:59:48.53Z" }, + { url = "https://files.pythonhosted.org/packages/2b/df/2a672eab99e0d0eba52d8a63e47dc92245eee26954d1b2d3c8f7d372151f/coverage-7.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e", size = 248143, upload-time = "2026-02-03T13:59:50.027Z" }, + { url = "https://files.pythonhosted.org/packages/a5/dc/a104e7a87c13e57a358b8b9199a8955676e1703bb372d79722b54978ae45/coverage-7.13.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56", size = 246148, upload-time = "2026-02-03T13:59:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/e113d3a58dc20b03b7e59aed1e53ebc9ca6167f961876443e002b10e3ae9/coverage-7.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f", size = 246414, upload-time = "2026-02-03T13:59:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/3f/60/a3fd0a6e8d89b488396019a2268b6a1f25ab56d6d18f3be50f35d77b47dc/coverage-7.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a", size = 247023, upload-time = "2026-02-03T13:59:55.454Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/de4840bb939dbb22ba0648a6d8069fa91c9cf3b3fca8b0d1df461e885b3d/coverage-7.13.3-cp310-cp310-win32.whl", hash = "sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be", size = 221751, upload-time = "2026-02-03T13:59:57.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/87/233ff8b7ef62fb63f58c78623b50bef69681111e0c4d43504f422d88cda4/coverage-7.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b", size = 222686, upload-time = "2026-02-03T13:59:58.825Z" }, + { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, + { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, + { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, + { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, + { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, + { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, + { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, + { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, + { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, + { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, + { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, + { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, + { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, + { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, + { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, + { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, + { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, + { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, + { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, + { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, + { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, + { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -1797,6 +1896,7 @@ advanced = [ [package.dev-dependencies] dev = [ { name = "autopep8" }, + { name = "coverage" }, { name = "mypy" }, { name = "pre-commit" }, { name = "pytest" }, @@ -1837,6 +1937,7 @@ provides-extras = ["advanced"] [package.metadata.requires-dev] dev = [ { name = "autopep8" }, + { name = "coverage" }, { name = "mypy" }, { name = "pre-commit" }, { name = "pytest" }, From 801f86e56eeeb7cdf27a1ef2db95d4ee469d4577 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 6 Feb 2026 20:06:18 +0000 Subject: [PATCH 31/72] ci: added github action for coverage --- .../workflows/codacy-coverage-reporter.yaml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/codacy-coverage-reporter.yaml diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml new file mode 100644 index 0000000..92e89e2 --- /dev/null +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -0,0 +1,35 @@ +# This workflow uploads PyProximal coverage analysis on Codacy +# For more information see: https://github.com/codacy/codacy-coverage-reporter-action +name: PyProximal-coverage + +on: [push, pull_request_target] + +jobs: + build: + strategy: + matrix: + platform: [ ubuntu-latest, ] + python-version: ["3.11", ] + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Install uv with Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and pyproximal + run: uv sync --locked --all-extras --all-groups + - name: Coverage with pytest + run: | + uv coverage run -m pytest + uv coverage xml + - name: Run codacy-coverage-reporter + uses: codacy/codacy-coverage-reporter-action@v1 + with: + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + coverage-reports: coverage.xml From fa17a35ebab08d089922c8e12c157f77a8298bad Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 6 Feb 2026 20:37:18 +0000 Subject: [PATCH 32/72] minor: make coverage GA safe --- .github/workflows/codacy-coverage-reporter.yaml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index 92e89e2..e7bb987 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -2,33 +2,43 @@ # For more information see: https://github.com/codacy/codacy-coverage-reporter-action name: PyProximal-coverage -on: [push, pull_request_target] +on: + pull_request: + push: + branches: [main] jobs: build: strategy: matrix: - platform: [ ubuntu-latest, ] - python-version: ["3.11", ] + platform: [ubuntu-latest] + python-version: ["3.11"] runs-on: ${{ matrix.platform }} + steps: - uses: actions/checkout@v4 + - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Install uv with Python ${{ matrix.python-version }} uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies and pyproximal run: uv sync --locked --all-extras --all-groups + - name: Coverage with pytest run: | uv coverage run -m pytest uv coverage xml + - name: Run codacy-coverage-reporter + if: github.event_name == 'push' uses: codacy/codacy-coverage-reporter-action@v1 with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} From 20b237dc55ce382a0a53f3a4aeafd1adedb18709 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 6 Feb 2026 20:38:43 +0000 Subject: [PATCH 33/72] minor: fix command in coverage GA --- .github/workflows/codacy-coverage-reporter.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index e7bb987..3336ae0 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -34,8 +34,8 @@ jobs: - name: Coverage with pytest run: | - uv coverage run -m pytest - uv coverage xml + uv run coverage -m pytest + uv run coverage xml - name: Run codacy-coverage-reporter if: github.event_name == 'push' From 6936615f727cad4a8a6fc62684a687aee311f766 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 6 Feb 2026 20:40:58 +0000 Subject: [PATCH 34/72] minor: one more fix in coverage GA --- .github/workflows/codacy-coverage-reporter.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index 3336ae0..43908a0 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -34,7 +34,7 @@ jobs: - name: Coverage with pytest run: | - uv run coverage -m pytest + uv run coverage run -m pytest uv run coverage xml - name: Run codacy-coverage-reporter From a34c95db26ee1d0e8d14b4587ed51ad62a831bc9 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Fri, 6 Feb 2026 20:44:20 +0000 Subject: [PATCH 35/72] minor: upload coverage.html as artifact in GA --- .github/workflows/codacy-coverage-reporter.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index 43908a0..eb5c7b3 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -36,6 +36,13 @@ jobs: run: | uv run coverage run -m pytest uv run coverage xml + uv run coverage html + + - name: Upload HTML coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-html + path: htmlcov/ - name: Run codacy-coverage-reporter if: github.event_name == 'push' From 25b85cedd011db4c6eb6bec17171728b1319da65 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 7 Feb 2026 19:54:19 +0000 Subject: [PATCH 36/72] doc: added reference for GeneralizedProximalGradient --- pyproximal/optimization/primal.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index f7a568a..423707a 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -783,7 +783,7 @@ def GeneralizedProximalGradient( Notes ----- The Generalized Proximal gradient algorithm can be expressed by the - following recursion: + following recursion [1]_: .. math:: \text{for } j=1,\cdots,n, \\ @@ -795,6 +795,9 @@ def GeneralizedProximalGradient( where :math:`\sum_{j=1}^n w_j=1`. In the current implementation, :math:`w_j=1/n` when not provided. + .. [1] Raguet, H., Fadili, J. and Peyré, G. "Generalized Forward-Backward Splitting", + arXiv, 2012. + """ # check if weights sum to 1 if weights is None: @@ -1915,7 +1918,7 @@ def PPXA( # pylint: disable=invalid-name # initialize model m = len(proxfs) if weights is None: - w = ncp.full(m, 1. / m) + w = ncp.full(m, 1.0 / m) else: w = ncp.asarray(weights) @@ -1944,8 +1947,7 @@ def PPXA( # pylint: disable=invalid-name if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: pf = ncp.sum([proxfs[i](x) for i in range(m)]) print( - f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} " - f"{pf:10.3e}" + f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} " f"{pf:10.3e}" ) # break if tolerance condition is met From efdfd9caf902687c9d411bf7cb0fd952b733b1cb Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 9 Feb 2026 20:22:08 +0000 Subject: [PATCH 37/72] minor: fix invalid escape and make ruff behave like ruff --- docs/source/conf.py | 107 ++++++++++++++++++++++++-------------------- pyproject.toml | 3 ++ 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 012934c..8a77055 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -7,25 +7,26 @@ from pyproximal import __version__ -# Sphinx needs to be able to import the package to use autodoc and get the version number -sys.path.insert(0, os.path.abspath('../../pyproximal')) +# Sphinx needs to be able to import the package to use autodoc and +# get the version number +sys.path.insert(0, os.path.abspath("../../pyproximal")) extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.doctest', - 'sphinx.ext.viewcode', - 'sphinx.ext.extlinks', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.doctest", + "sphinx.ext.viewcode", + "sphinx.ext.extlinks", "sphinx.ext.intersphinx", - 'matplotlib.sphinxext.plot_directive', - 'numpydoc', - 'nbsphinx', + "matplotlib.sphinxext.plot_directive", + "numpydoc", + "nbsphinx", "sphinx_design", "sphinx_iconify", - 'sphinx_gallery.gen_gallery', - 'sphinxemoji.sphinxemoji', + "sphinx_gallery.gen_gallery", + "sphinxemoji.sphinxemoji", ] # intersphinx configuration @@ -44,10 +45,10 @@ autosummary_generate = True # Include Python objects as they appear in source files -autodoc_member_order = 'bysource' +autodoc_member_order = "bysource" # Default flags used by autodoc directives -autodoc_default_flags = ['members'] +autodoc_default_flags = ["members"] numpydoc_show_class_members = False numpydoc_show_inherited_class_members = False @@ -55,46 +56,49 @@ sphinx_gallery_conf = { # path to your examples scripts - 'examples_dirs': ['../../examples', '../../tutorials',], + "examples_dirs": [ + "../../examples", + "../../tutorials", + ], # path where to save gallery generated examples - 'gallery_dirs': ['gallery', 'tutorials'], - 'filename_pattern': '\.py', + "gallery_dirs": ["gallery", "tutorials"], + "filename_pattern": r"\.py", # Remove the "Download all examples" button from the top level gallery - 'download_all_examples': False, + "download_all_examples": False, # Sort gallery example by file name instead of number of lines (default) - 'within_subsection_order': ExampleTitleSortKey, + "within_subsection_order": ExampleTitleSortKey, # directory where function granular galleries are stored - 'backreferences_dir': 'api/generated/backreferences', + "backreferences_dir": "api/generated/backreferences", # Modules for which function level galleries are created. - 'doc_module': 'pyproximal', + "doc_module": "pyproximal", # Insert links to documentation of objects in the examples - 'reference_url': {'pyproximal': None}, + "reference_url": {"pyproximal": None}, # Allow animations - 'matplotlib_animations': True, + "matplotlib_animations": True, } # Always show the source code that generates a plot plot_include_source = True -plot_formats = ['png'] +plot_formats = ["png"] # Sphinx project configuration -templates_path = ['_templates'] +templates_path = ["_templates"] exclude_patterns = ["_build", "**.ipynb_checkpoints", "**.ipynb", "**.md5"] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -source_encoding = 'utf-8-sig' -master_doc = 'index' +source_encoding = "utf-8-sig" +master_doc = "index" # General information about the project year = datetime.date.today().year -project = 'PyProximal' +project = "PyProximal" copyright = "{}, PyLops Development Team".format(year) # Version version = __version__ -if len(version.split('+')) > 1 or version == 'unknown': - version = 'dev' +if len(version.split("+")) > 1 or version == "unknown": + version = "dev" # These enable substitutions using |variable| in the rst files rst_epilog = """ @@ -102,12 +106,12 @@ """.format(year=year) html_static_path = ["_static"] -html_last_updated_fmt = '%b %d, %Y' -html_title = 'PyProximal' -html_short_title = 'PyProximal' +html_last_updated_fmt = "%b %d, %Y" +html_title = "PyProximal" +html_short_title = "PyProximal" html_favicon = "_static/favicon.ico" html_extra_path = [] -pygments_style = 'default' +pygments_style = "default" add_function_parentheses = False html_show_sourcelink = False html_show_sphinx = True @@ -126,20 +130,27 @@ ] html_context = { - 'menu_links_name': 'Repository', - 'menu_links': [ - (' Source Code', 'https://github.com/PyLops/pyproximal'), - (' Contributing', 'https://github.com/PyLops/pyproximal/blob/master/CONTRIBUTING.md'), + "menu_links_name": "Repository", + "menu_links": [ + ( + ' Source Code', + "https://github.com/PyLops/pyproximal", + ), + ( + ' Contributing', + "https://github.com/PyLops/pyproximal/blob/master/CONTRIBUTING.md", + ), ], # Custom variables to enable "Improve this page"" and "Download notebook" # links - 'doc_path': 'docs/source', - 'galleries': sphinx_gallery_conf['gallery_dirs'], - 'gallery_dir': dict(zip(sphinx_gallery_conf['gallery_dirs'], - sphinx_gallery_conf['examples_dirs'])), - 'github_project': 'PyLops', - 'github_repo': 'pyproximal', - 'github_version': 'master', + "doc_path": "docs/source", + "galleries": sphinx_gallery_conf["gallery_dirs"], + "gallery_dir": dict( + zip(sphinx_gallery_conf["gallery_dirs"], sphinx_gallery_conf["examples_dirs"]) + ), + "github_project": "PyLops", + "github_repo": "pyproximal", + "github_version": "master", } # Math macros diff --git a/pyproject.toml b/pyproject.toml index 11505f8..9b862b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,9 @@ python_files = ["pytests/*.py"] src = ["pyproximal"] line-length = 88 +[tool.ruff.lint] +select = ["E", "F", "W"] + [tool.ruff.lint.per-file-ignores] "__init__.py" = [ "F401", # imported but unused (re-exports) From dfc5610648a22208d190ba207038422db8425d3c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 9 Feb 2026 21:17:51 +0000 Subject: [PATCH 38/72] minor: removed commented windows job in azure pipeline --- azure-pipelines.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 34f2232..165e2c7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,35 +11,6 @@ trigger: jobs: -# Windows -######################################################################################## -# - job: -# displayName: 'Windows' -# -# pool: -# vmImage: 'windows-2019' -# -# variables: -# NUMBA_NUM_THREADS: 1 -# -# steps: -# - task: UsePythonVersion@0 -# inputs: -# versionSpec: '3.11' -# architecture: 'x64' -# -# - script: | -# python -m pip install --upgrade pip setuptools wheel django -# pip install -r requirements-dev.txt -# pip install . -# displayName: 'Install prerequisites and library' -# -# - script: | -# python setup.py test -# condition: succeededOrFailed() -# displayName: 'Run tests' - - # Mac ######################################################################################## - job: From 2234e6d05baede4a8fe24a837038bf0cee06330c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 9 Feb 2026 21:19:03 +0000 Subject: [PATCH 39/72] minor: temporarely restore working ruff --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9b862b9..11505f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,9 +84,6 @@ python_files = ["pytests/*.py"] src = ["pyproximal"] line-length = 88 -[tool.ruff.lint] -select = ["E", "F", "W"] - [tool.ruff.lint.per-file-ignores] "__init__.py" = [ "F401", # imported but unused (re-exports) From 0235ecb4a9293a3310bf8ac6ef2bb867ba2d2e1e Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 9 Feb 2026 21:48:52 +0000 Subject: [PATCH 40/72] ci: modified azure pipeline to use uv --- azure-pipelines.yml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 165e2c7..369e5b8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,15 +29,18 @@ jobs: architecture: 'arm64' - script: | - python -m pip install --upgrade pip setuptools wheel django - pip install -r requirements-dev.txt - pip install . - displayName: 'Install prerequisites and library' + python -m pip install --upgrade pip + pip install uv + displayName: 'Install uv' - script: | - pytest + uv sync --locked --all-extras --all-groups + displayName: 'Install dependencies and pyproximal' + + - script: | + uv run pytest --color=yes pytests/ condition: succeededOrFailed() - displayName: 'Run tests' + displayName: 'Test with pytest' # Linux @@ -58,12 +61,15 @@ jobs: architecture: 'x64' - script: | - python -m pip install --upgrade pip setuptools wheel django - pip install -r requirements-dev.txt - pip install . - displayName: 'Install prerequisites and library' + python -m pip install --upgrade pip + pip install uv + displayName: 'Install uv' + + - script: | + uv sync --locked --all-extras --all-groups + displayName: 'Install dependencies and pyproximal' - script: | - pytest + uv run pytest --color=yes pytests/ condition: succeededOrFailed() - displayName: 'Run tests' + displayName: 'Test with pytest' From 308a007acd6383861d3dab2c82c27b4539b45a6c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 9 Feb 2026 22:08:57 +0000 Subject: [PATCH 41/72] feat: stop using and mentioning requirements files --- .pre-commit-config.yaml | 1 - Makefile | 10 +-------- README.md | 17 +++++++++----- docs/source/installation.rst | 43 ++++++++++-------------------------- requirements-dev.txt | 28 ----------------------- requirements.txt | 1 - 6 files changed, 24 insertions(+), 76 deletions(-) delete mode 100644 requirements-dev.txt delete mode 100644 requirements.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9def2cc..87afb0a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,6 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: mixed-line-ending - - id: requirements-txt-fixer - id: check-toml - id: check-yaml - id: check-added-large-files diff --git a/Makefile b/Makefile index 40c6106..5579da0 100755 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PIP := $(shell command -v pip3 2> /dev/null || command which pip 2> /dev/null) PYTHON := $(shell command -v python3 2> /dev/null || command which python 2> /dev/null) UV := $(shell command -v uv 2> /dev/null || command which uv 2> /dev/null) -.PHONY: install dev-install install_conda dev-install_conda +.PHONY: install_conda dev-install_conda .PHONY: tests tests_uv doc doc_uv docupdate docupdate_uv servedoc .PHONY: lint lint_uv typeannot typeannot_uv coverage, coverage_uv @@ -24,14 +24,6 @@ ifndef UV endif @echo Using uv: $(UV) -install: - make pipcheck - $(PIP) install -r requirements.txt && $(PIP) install . - -dev-install: - make pipcheck - $(PIP) install -r requirements-dev.txt && $(PIP) install -e . - install_conda: conda env create -f environment.yml && source ${CONDA_PREFIX}/etc/profile.d/conda.sh && conda activate pyproximal && pip install . diff --git a/README.md b/README.md index 0ba6c86..ddc7b8e 100644 --- a/README.md +++ b/README.md @@ -105,22 +105,26 @@ This repository is organized as follows: * **tutorials**: set of python script tutorials to be embedded in documentation using sphinx-gallery ## Getting started -You need **Python 3.8 or greater**. +You need **Python 3.10 or greater**. -*Note: Versions prior to v0.3.0 work also with Python 3.6 or greater, however they -require scipy version to be lower than v1.8.0.* +#### From Conda To get the most out of PyLops straight out of the box, we recommend `conda` to install PyLops: ```bash conda install -c conda-forge pyproximal ``` -#### From PyPi +#### From PyPI You can also install pyproximal with `pip`: ```bash pip install pyproximal ``` +or via `uv` (reccomended): +```bash +uv pip install pyproximal +``` + #### From Github Finally, you can also directly install from the main branch (although this is not recommended) @@ -144,8 +148,9 @@ git clone https://github.com/your_name_here/pyproximal.git ``` ### 2. Install PyLops in a new Conda environment -To ensure that further development of PyLops is performed within the same environment (i.e., same dependencies) as -that defined by ``requirements-dev.txt`` or ``environment-dev.yml`` files, we suggest to work off a new Conda enviroment. +To ensure that further development of PyLops is performed within the same environment (i.e., +same dependencies) as that defined by ``environment-dev.yml``/``environment-dev-arm.yml`` files, +we suggest to work off a new Conda enviroment. The first time you clone the repository run the following command: ``` diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 9fcc9a4..62a4690 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -13,11 +13,14 @@ The mandatory dependencies of PyProximal are limited to: * `SciPy `_ * `PyLops `_ -We highly encourage using the `Anaconda Python distribution `_ +We encourage using the `Anaconda Python distribution `_ or its standalone package manager `Conda `_. Especially for Intel processors, this ensures a higher performance with no configuration (e.g., the linking to ``Intel MKL`` library, a highly optimized BLAS library created by Intel). -For learning, however, the standard installation is often good enough. + +For learning, however, the standard installation is often good enough; in that case, we +recommend using `uv `_, a modern Python package manager that +is easy to use and has a very fast dependency resolver. Step-by-step installation for users @@ -37,19 +40,6 @@ First install `pyproximal` with your package manager of choice. Most of the dependencies (all required and some of the optional) are automatically installed for you. - - .. tab-item:: :iconify:`devicon:pypi` pip - - .. code-block:: bash - - >> pip install pyproximal - - Only the *required* dependencies are installed. To install - some of the optional dependencies, run: - - .. code-block:: bash - - >> pip install "pyproximal[advanced]" .. tab-item:: :iconify:`material-icon-theme:uv` uv @@ -114,23 +104,14 @@ For that end, we provide a `Makefile` with useful commands for setting up the en This creates and activate an environment called ``pyproximal``, with all required and optional dependencies. - - .. tab-item:: :iconify:`devicon:pypi` pip - - .. code-block:: bash - - >> make dev-install - - Thid does not create a virtual environment. Make sure you create and - activate your environment before running this command. .. tab-item:: :iconify:`material-icon-theme:uv` uv .. code-block:: bash - >> uv sync + >> uv sync --locked --all-extras --all-groups - This also creates a virtual environment `.venv` that can be activated at + This creates a virtual environment `.venv` that can be activated at any time with `source .venv/bin/activate` (Linux/macOS). Run tests @@ -139,7 +120,7 @@ To ensure that everything has been setup correctly, run tests: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: :iconify:`devicon:anaconda` .. code-block:: bash @@ -180,7 +161,7 @@ In order to setup such hooks in your local repository, run: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: :iconify:`devicon:anaconda` .. code-block:: bash @@ -202,7 +183,7 @@ A properly configured linter (``ruff``) can be run with: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: :iconify:`devicon:anaconda` .. code-block:: bash @@ -220,7 +201,7 @@ Build (or update) the docs with: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: :iconify:`devicon:anaconda` .. code-block:: bash @@ -236,7 +217,7 @@ or .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: :iconify:`devicon:anaconda` .. code-block:: bash diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 9b3f58a..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,28 +0,0 @@ -autopep8 -bm3d -bm4d -coverage -image -ipython -matplotlib -mypy -nbsphinx -numba -numpy>=1.21.0 -numpydoc -pooch -pre-commit -pylops>=2.0.0 -pytest -pytest-runner -ruff -scikit-image -scipy>=1.11.0 -setuptools_scm -shibuya -sphinx -sphinx-design -sphinx-gallery -sphinx-iconify -sphinxemoji -typing_extensions diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9c558e3..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -. From 25a9b2052c03a176850f12a1e0cedae35d986dbd Mon Sep 17 00:00:00 2001 From: mrava87 Date: Mon, 9 Feb 2026 22:14:33 +0000 Subject: [PATCH 42/72] minor: change version for typing_extensions --- environment-dev-arm.yml | 2 +- environment-dev.yml | 2 +- environment.yml | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/environment-dev-arm.yml b/environment-dev-arm.yml index e262e63..d386d10 100644 --- a/environment-dev-arm.yml +++ b/environment-dev-arm.yml @@ -10,7 +10,7 @@ dependencies: - pylops>=2.0.0 - scikit-image - matplotlib - - typing_extensions + - typing_extensions>=4.0.0 - ipython - pytest - Sphinx diff --git a/environment-dev.yml b/environment-dev.yml index 8af0220..e39a571 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -10,7 +10,7 @@ dependencies: - pylops>=2.0.0 - scikit-image - matplotlib - - typing_extensions + - typing_extensions>=4.0.0 - ipython - pytest - Sphinx diff --git a/environment.yml b/environment.yml index 22569fa..1a5ca30 100644 --- a/environment.yml +++ b/environment.yml @@ -6,4 +6,4 @@ dependencies: - numpy>=1.21.0 - scipy>=1.14.0 - pylops>=2.0.0 - - typing_extensions + - typing_extensions>=4.0.0 diff --git a/pyproject.toml b/pyproject.toml index 11505f8..d4cbedf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "numpy >= 1.21.0", "scipy >= 1.11.0", "pylops >= 2.0.0", - "typing_extensions", + "typing_extensions >= 4.0.0", ] dynamic = ["version"] diff --git a/uv.lock b/uv.lock index a8f2384..513612f 100644 --- a/uv.lock +++ b/uv.lock @@ -1930,7 +1930,7 @@ requires-dist = [ { name = "numpy", specifier = ">=1.21.0" }, { name = "pylops", specifier = ">=2.0.0" }, { name = "scipy", specifier = ">=1.11.0" }, - { name = "typing-extensions" }, + { name = "typing-extensions", specifier = ">=4.0.0" }, ] provides-extras = ["advanced"] From c431f9f9319f1c036efe19223cb33fb008235967 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 10 Feb 2026 08:16:08 +0000 Subject: [PATCH 43/72] doc: stop using style.css --- docs/source/_static/style.css | 117 ---------------------------------- docs/source/conf.py | 5 -- 2 files changed, 122 deletions(-) delete mode 100644 docs/source/_static/style.css diff --git a/docs/source/_static/style.css b/docs/source/_static/style.css deleted file mode 100644 index 94b9bef..0000000 --- a/docs/source/_static/style.css +++ /dev/null @@ -1,117 +0,0 @@ -/* To stick the footer to the bottom of the page */ -html { -} - -body { - font-family: 'Open Sans', sans-serif; -} - -h1, h2, h3, h4 { - font-weight: 300; - font-family: "Open Sans",sans-serif; -} - -h1 { - font-size: 200%; -} - -.sidebar-title { - margin-top: 10px; - margin-bottom: 0px; -} - -.banner { - padding-bottom: 60px; - text-align: center; -} - -.banner img { - margin-bottom: 40px; -} - -.api-module { - margin-bottom: 80px; -} - -.youtube-embed { - max-width: 600px; - margin-bottom: 24px; -} - -.video-container { - position:relative; - padding-bottom:56.25%; - padding-top:30px; - height:0; - overflow:hidden; -} - -.video-container iframe, .video-container object, .video-container embed { - position:absolute; - top:0; - left:0; - width:100%; - height:100%; -} - -.wy-nav-content { - max-width: 1000px; -} - -.wy-nav-top { - background-color: #555555; -} - -.wy-side-nav-search { - background-color: #555555; -} - -.wy-side-nav-search > a img.logo { - width: 50%; -} - -.wy-side-nav-search input[type="text"] { - border-color: #555555; -} - -/* Remove the padding from the Parameters table */ -.rst-content table.field-list .field-name { - padding-left: 0px; -} - -/* Lign up the Parameters section with the descriptions */ -.rst-content table.field-list td { - padding-top: 8px; -} - -.rst-content .highlight > pre { - font-size: 14px; -} - -.rst-content img { - max-width: 100%; -} - -.source-link { - float: right; -} - -.strike { - text-decoration: line-through; -} - -/* Don't let the edit and notebook download links disappear on mobile. */ -@media screen and (max-width: 480px) { - .wy-breadcrumbs li.source-link { - float:none; - display: block; - margin-top: 20px; - } -} - -/* Sphinx-Gallery */ -/****************************************************************************/ -/* Don't let captions be italic */ -.rst-content div.figure p.caption { - font-style: normal; -} \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 8a77055..c6da920 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -165,8 +165,3 @@ } } } - - -# Load the custom CSS files (needs sphinx >= 1.6 for this to work) -def setup(app): - app.add_css_file("style.css") From 5770c387c64c38080e3c2643e7b52bcddd236a89 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 10 Feb 2026 08:40:41 +0000 Subject: [PATCH 44/72] minor: updated README to match new documentation --- .pre-commit-config.yaml | 1 - README.md | 14 +++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87afb0a..7af5843 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,6 @@ repos: - id: check-merge-conflict - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. rev: v0.14.14 hooks: - id: ruff-check diff --git a/README.md b/README.md index ddc7b8e..231b558 100644 --- a/README.md +++ b/README.md @@ -119,18 +119,21 @@ You can also install pyproximal with `pip`: ```bash pip install pyproximal ``` - -or via `uv` (reccomended): +or via `uv`: ```bash uv pip install pyproximal ``` #### From Github -Finally, you can also directly install from the main branch (although this is not recommended) +Finally, you can also directly install from the main branch (although this is not recommended): ``` pip install git+https://git@github.com/PyLops/pyproximal.git@main ``` +or via `uv`: +```bash +uv add git+https://github.com/PyLops/pyproximal.git --branch main +``` ## Contributing *Feel like contributing to the project? Adding new operators or tutorial?* @@ -167,6 +170,9 @@ Remember to always activate the conda environment every time you open a new term source activate pyproximal ``` +If you prefer to use ``uv``, checkout the [Contributing](https://pyproximal.readthedocs.io/en/latest/contributing.html) page in the official documentation. + + ## Documentation The official documentation of PyProximal is available [here](https://pyproximal.readthedocs.io/). @@ -190,8 +196,6 @@ When using PyProximal in scientific publications, please cite the following pape - Ravasi M, Örnhag M. V., Luiken N., Leblanc O. and Uruñuela E., 2024, *PyProximal - scalable convex optimization in Python*, Journal of Open Source Software, 9(95), 6326. doi: 10.21105/joss.06326 [(link)](https://joss.theoj.org/papers/10.21105/joss.06326) - - ## Contributors * Matteo Ravasi, mrava87 * Nick Luiken, NickLuiken From ee375c789f021b98fe624ecd0a47253494014b51 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 10 Feb 2026 17:30:09 +0000 Subject: [PATCH 45/72] build: added support for nox --- Makefile | 21 ++++++++++++--- docs/source/contributing.rst | 18 ++++++++++--- noxfile.py | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 noxfile.py diff --git a/Makefile b/Makefile index 5579da0..2a476a8 100755 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ PIP := $(shell command -v pip3 2> /dev/null || command which pip 2> /dev/null) PYTHON := $(shell command -v python3 2> /dev/null || command which python 2> /dev/null) UV := $(shell command -v uv 2> /dev/null || command which uv 2> /dev/null) +NOX := $(shell command -v nox 2> /dev/null || command which nox 2> /dev/null) -.PHONY: install_conda dev-install_conda -.PHONY: tests tests_uv doc doc_uv docupdate docupdate_uv servedoc -.PHONY: lint lint_uv typeannot typeannot_uv coverage, coverage_uv +.PHONY: install_conda dev-install_conda dev-install_conda_arm dev-install_uv +.PHONY: tests tests_uv tests_nox doc doc_uv docupdate docupdate_uv servedoc +.PHONY: lint lint_uv typeannot typeannot_uv coverage coverage_uv pipcheck: ifndef PIP @@ -24,6 +25,12 @@ ifndef UV endif @echo Using uv: $(UV) +noxcheck: +ifndef NOX + $(error "Ensure nox is in your PATH") +endif + @echo Using nox: $(NOX) + install_conda: conda env create -f environment.yml && source ${CONDA_PREFIX}/etc/profile.d/conda.sh && conda activate pyproximal && pip install . @@ -33,6 +40,10 @@ dev-install_conda: dev-install_conda_arm: conda env create -f environment-dev-arm.yml && source ${CONDA_PREFIX}/etc/profile.d/conda.sh && conda activate pyproximal && pip install -e . +dev-install_uv: + make uvcheck + $(UV) sync --locked --all-extras --all-groups + tests: make pythoncheck pytest @@ -41,6 +52,10 @@ tests_uv: make uvcheck $(UV) run pytest +tests_nox: + make noxcheck + $(NOX) -s tests + doc: cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\ rm -rf source/tutorials && rm -rf source/examples &&\ diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 7131473..2272ec7 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -67,7 +67,7 @@ that both old and new tests pass successfully: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash @@ -78,13 +78,23 @@ that both old and new tests pass successfully: .. code-block:: bash >> make tests_uv + + .. tab-item:: :iconify:`material-icon-theme:foxpro` nox -4. Run ruff to check the quality of your code: + .. code-block:: bash + >> make tests_nox + + Whilst not enforced, this is recommended as it runs the tests + with different versions of Python (the same that are used in our CI). + Note that you need to have `nox` installed to run this command - + use `pipx install nox` or `brew install nox` on macOS to install it. + +4. Run ruff to check the quality of your code: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash @@ -103,7 +113,7 @@ will also be run as part of our CI. .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` \ :iconify:`devicon:pypi` pip + .. tab-item:: conda .. code-block:: bash diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..7e403a7 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import shutil +from pathlib import Path + +import nox + +DIR = Path(__file__).parent.resolve() + +nox.options.default_venv_backend = "uv" +nox.options.sessions = ["lint", "tests"] + + +@nox.session +def lint(session): + """Run the Ruff linter.""" + session.install("ruff") + session.run( + "ruff", + "check", + "docs/source", + "examples/", + "pyproximal/", + "pytests/", + "tutorials/", + ) + + +@nox.session(python=["3.10", "3.11", "3.12", "3.13", "3.14"]) +def tests(session: nox.Session) -> None: + """ + Run unit tests. + """ + session.install("-e", ".[advanced]") + session.install("--group", "dev") + session.run("pytest", *session.posargs) + + +@nox.session(python=["3.10", "3.11", "3.12", "3.13", "3.14"]) +def build(session: nox.Session) -> None: + """ + Build an SDist and wheel. + """ + + build_path = DIR.joinpath("build") + if build_path.exists(): + shutil.rmtree(build_path) + + session.install("build") + session.run("python", "-m", "build") From bb93715d27f6432a8b852047888280653595ca6d Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 10 Feb 2026 17:30:37 +0000 Subject: [PATCH 46/72] minor: small improvements to various doc files --- CONTRIBUTING.md | 4 +--- docs/source/installation.rst | 16 ++++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3564b4c..d99b499 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,8 +46,6 @@ Ready to contribute? 1. Follow all installation instructions in the [Step-by-step installation for developers](https://pyproximal.readthedocs.io/en/stable/installation.html#step-by-step-installation-for-developers) section of the documentation. -1. Fork the `PyProximal` repo. - 2. Create a branch for local development: ``` git checkout -b name-of-your-branch @@ -79,7 +77,7 @@ Ready to contribute? make docupdate ``` -6. Commit your changes and push your branch to GitLab:: +6. Commit your changes and push your branch to GitHub: ``` git add . git commit -m "Your detailed description of your changes." diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 62a4690..3af93dc 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -95,7 +95,7 @@ For that end, we provide a `Makefile` with useful commands for setting up the en .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` conda + .. tab-item:: conda .. code-block:: bash @@ -109,8 +109,8 @@ For that end, we provide a `Makefile` with useful commands for setting up the en .. code-block:: bash - >> uv sync --locked --all-extras --all-groups - + >> make dev-install_uv + This creates a virtual environment `.venv` that can be activated at any time with `source .venv/bin/activate` (Linux/macOS). @@ -120,7 +120,7 @@ To ensure that everything has been setup correctly, run tests: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash @@ -161,7 +161,7 @@ In order to setup such hooks in your local repository, run: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash @@ -183,7 +183,7 @@ A properly configured linter (``ruff``) can be run with: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash @@ -201,7 +201,7 @@ Build (or update) the docs with: .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash @@ -217,7 +217,7 @@ or .. tab-set:: - .. tab-item:: :iconify:`devicon:anaconda` + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash From 9c1713ba64fed999a1ebe8b7df5e7c8392c8bdf8 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 10 Feb 2026 17:38:56 +0000 Subject: [PATCH 47/72] minor: increase maxiter in segmentation tutorial --- tutorials/segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/segmentation.py b/tutorials/segmentation.py index 55e0edf..e8adc6a 100644 --- a/tutorials/segmentation.py +++ b/tutorials/segmentation.py @@ -60,7 +60,7 @@ sigma, alpha, niter=10, - kwargs_simplex=dict(maxiter=20, engine="numba", call=False), + kwargs_simplex=dict(maxiter=30, engine="numba", call=False), show=False, ) From 91ff0a958027200d55e7235ac69047a1c2871f26 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 17 Feb 2026 21:51:27 +0000 Subject: [PATCH 48/72] feat: add stricter ruff rules --- docs/source/conf.py | 20 ++- examples/plot_concave_penalties.py | 1 - examples/plot_dykstra.py | 1 - examples/plot_indicators.py | 1 - examples/plot_norms.py | 1 - examples/plot_quadratic.py | 1 - pyproject.toml | 20 ++- pyproximal/ProxOperator.py | 38 +++-- pyproximal/optimization/bregman.py | 7 +- pyproximal/optimization/palm.py | 36 ++-- pyproximal/optimization/pnp.py | 7 +- pyproximal/optimization/primal.py | 168 +++++++++---------- pyproximal/optimization/primaldual.py | 22 +-- pyproximal/optimization/segmentation.py | 15 +- pyproximal/optimization/sr3.py | 4 +- pyproximal/projection/Box.py | 10 +- pyproximal/projection/Euclidean.py | 4 +- pyproximal/projection/GenericIntersection.py | 14 +- pyproximal/projection/Intersection.py | 6 +- pyproximal/projection/L0.py | 8 +- pyproximal/proximal/Box.py | 6 +- pyproximal/proximal/ETP.py | 6 +- pyproximal/proximal/Euclidean.py | 4 +- pyproximal/proximal/Geman.py | 10 +- pyproximal/proximal/GenericIntersection.py | 5 +- pyproximal/proximal/Intersection.py | 4 +- pyproximal/proximal/L0.py | 6 +- pyproximal/proximal/L1.py | 7 +- pyproximal/proximal/L2.py | 24 +-- pyproximal/proximal/Log.py | 9 +- pyproximal/proximal/Nonlinear.py | 5 +- pyproximal/proximal/Nuclear.py | 4 +- pyproximal/proximal/Orthogonal.py | 4 +- pyproximal/proximal/Quadratic.py | 7 +- pyproximal/proximal/QuadraticEnvelope.py | 5 +- pyproximal/proximal/RelaxedMS.py | 5 +- pyproximal/proximal/SCAD.py | 8 +- pyproximal/proximal/Simplex.py | 17 +- pyproximal/proximal/Sum.py | 18 +- pyproximal/proximal/TV.py | 8 +- pyproximal/proximal/VStack.py | 25 +-- pyproximal/proximal/_Dykstra.py | 14 +- pyproximal/proximal/_Simplex_cuda.py | 2 +- pyproximal/proximal/_Simplex_numba.py | 2 +- pyproximal/utils/__init__.py | 2 +- pyproximal/utils/bilinear.py | 14 +- pyproximal/utils/gradtest.py | 8 +- pyproximal/utils/moreau.py | 3 +- pyproximal/utils/typing.py | 6 +- pyproximal/utils/utils.py | 5 +- pytests/test_basic.py | 4 +- pytests/test_bilinear.py | 1 - pytests/test_concave_penalties.py | 1 - pytests/test_dykstra.py | 21 +-- pytests/test_grads.py | 1 - pytests/test_norms.py | 1 - pytests/test_projection.py | 1 - pytests/test_proximal.py | 3 +- pytests/test_solver.py | 35 ++-- tutorials/adaptivepd.py | 3 +- tutorials/basispursuit.py | 1 - tutorials/brainmri.py | 3 +- tutorials/constrained.py | 1 - tutorials/deblending.py | 1 - tutorials/denoising.py | 3 +- tutorials/groupsparsity.py | 6 +- tutorials/hankel_matrix_estimation.py | 5 +- tutorials/lowrankcompletion.py | 3 +- tutorials/matrixfactorization.py | 1 - tutorials/nonlinearconstrained.py | 7 +- tutorials/nrsfm.py | 6 +- tutorials/plugandplay.py | 3 +- tutorials/relaxed_mumford-shah.py | 1 - tutorials/segmentation.py | 1 - tutorials/twist.py | 1 - 75 files changed, 377 insertions(+), 364 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index c6da920..1b468c9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- -import sys -import os import datetime - -from sphinx_gallery.sorting import ExampleTitleSortKey +import os +import sys from pyproximal import __version__ +from sphinx_gallery.sorting import ExampleTitleSortKey # Sphinx needs to be able to import the package to use autodoc and # get the version number @@ -93,7 +91,7 @@ # General information about the project year = datetime.date.today().year project = "PyProximal" -copyright = "{}, PyLops Development Team".format(year) +copyright = f"{year}, PyLops Development Team" # Version version = __version__ @@ -101,9 +99,9 @@ version = "dev" # These enable substitutions using |variable| in the rst files -rst_epilog = """ +rst_epilog = f""" .. |year| replace:: {year} -""".format(year=year) +""" html_static_path = ["_static"] html_last_updated_fmt = "%b %d, %Y" @@ -146,7 +144,11 @@ "doc_path": "docs/source", "galleries": sphinx_gallery_conf["gallery_dirs"], "gallery_dir": dict( - zip(sphinx_gallery_conf["gallery_dirs"], sphinx_gallery_conf["examples_dirs"]) + zip( + sphinx_gallery_conf["gallery_dirs"], + sphinx_gallery_conf["examples_dirs"], + strict=False, + ) ), "github_project": "PyLops", "github_repo": "pyproximal", diff --git a/examples/plot_concave_penalties.py b/examples/plot_concave_penalties.py index cbbaa0c..65cf921 100644 --- a/examples/plot_concave_penalties.py +++ b/examples/plot_concave_penalties.py @@ -7,7 +7,6 @@ import matplotlib.pyplot as plt import numpy as np - import pyproximal plt.close("all") diff --git a/examples/plot_dykstra.py b/examples/plot_dykstra.py index 6aa0605..485b738 100644 --- a/examples/plot_dykstra.py +++ b/examples/plot_dykstra.py @@ -15,7 +15,6 @@ from matplotlib.colors import to_rgba from matplotlib.patches import Circle, Rectangle from pylops import MatrixMult - from pyproximal.projection import BoxProj, EuclideanBallProj, GenericIntersectionProj from pyproximal.proximal import L1, L2, Box, EuclideanBall, GenericIntersectionProx, Sum diff --git a/examples/plot_indicators.py b/examples/plot_indicators.py index 9d70a8c..f03e4f3 100755 --- a/examples/plot_indicators.py +++ b/examples/plot_indicators.py @@ -8,7 +8,6 @@ import matplotlib.pyplot as plt import numpy as np - import pyproximal plt.close("all") diff --git a/examples/plot_norms.py b/examples/plot_norms.py index bd80d37..1f486c2 100755 --- a/examples/plot_norms.py +++ b/examples/plot_norms.py @@ -14,7 +14,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal plt.close("all") diff --git a/examples/plot_quadratic.py b/examples/plot_quadratic.py index a8a9795..8f93727 100755 --- a/examples/plot_quadratic.py +++ b/examples/plot_quadratic.py @@ -15,7 +15,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal plt.close("all") diff --git a/pyproject.toml b/pyproject.toml index d4cbedf..e57c81f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,25 @@ python_files = ["pytests/*.py"] [tool.ruff] src = ["pyproximal"] +exclude = ["pyproximal/version.py", ] line-length = 88 +target-version = "py310" + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "W", # pycodestyle warnings + "B", # flake8-bugbear + "EM", # flake8-errmsg + "UP", # pyupgrade + "I" # isort +] +ignore = [ + "E501", # line too long (handled by formatter like Black) + "I001", # allow usorted imports + "UP031", # use of old '%' formatting instead of f-strings +] [tool.ruff.lint.per-file-ignores] "__init__.py" = [ @@ -102,10 +120,10 @@ mypy_path = "pyproximal" strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] disable_error_code = ["untyped-decorator"] -warn_unreachable = true allow_redefinition = true disallow_untyped_defs = true disallow_incomplete_defs = true +warn_unreachable = false [[tool.mypy.overrides]] module = ["cupy.*", "numpy.*", "numba.*", "pylops.*", "scipy.*", "scooby.*", "pyproximal.*"] diff --git a/pyproximal/ProxOperator.py b/pyproximal/ProxOperator.py index 436952d..a3ff3ff 100644 --- a/pyproximal/ProxOperator.py +++ b/pyproximal/ProxOperator.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Callable, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -19,13 +20,14 @@ def _check_tau(func: Callable[..., NDArray]) -> Callable[..., NDArray]: def wrapper(*args: Any, **kwargs: Any) -> Any: if np.any(args[2] <= 0): - raise ValueError("tau must be positive") + msg = "tau must be positive" + raise ValueError(msg) return func(*args, **kwargs) return wrapper -class ProxOperator(object): +class ProxOperator: r"""Common interface for proximal operators of a function. This class defines the overarching structure of any proximal operator. It @@ -87,10 +89,11 @@ def __call__(self, x: NDArray) -> bool | float | int: Subclasses should implement this. Returns the value of the function. """ - raise NotImplementedError( + msg = ( "This ProxOperator's __call__ method " "must be implemented by subclasses to return a float." ) + raise NotImplementedError(msg) @_check_tau def _prox_moreau(self, x: NDArray, tau: float, **kwargs: Any) -> NDArray: @@ -207,7 +210,8 @@ def affine_addition(self, v: NDArray) -> "ProxOperator": if isinstance(v, (np.ndarray, cp_dtype)): return _SumOperator(self, v) else: - raise NotImplementedError("v must be a numpy.ndarray or cupy.ndarray") + msg = "v must be a numpy.ndarray or cupy.ndarray" + raise NotImplementedError(msg) def postcomposition(self, sigma: float) -> "ProxOperator": r"""Postcomposition @@ -235,7 +239,8 @@ def postcomposition(self, sigma: float) -> "ProxOperator": if isinstance(sigma, float): return _PostcompositionOperator(self, sigma) else: - raise NotImplementedError("sigma must be of type float") + msg = "sigma must be of type float" + raise NotImplementedError(msg) def precomposition(self, a: float, b: float | NDArray) -> "ProxOperator": r"""Precomposition @@ -264,11 +269,8 @@ def precomposition(self, a: float, b: float | NDArray) -> "ProxOperator": if isinstance(a, float) and isinstance(b, (float, np.ndarray, cp_dtype)): # type: ignore[redundant-expr] return _PrecompositionOperator(self, a, b) else: - raise NotImplementedError( - "a must be of type float and b " - "must be of type float or " - "numpy.ndarray" - ) + msg = "a must be of type float and b must be of type float or numpy.ndarray" + raise NotImplementedError(msg) def chain(self, g: "ProxOperator") -> "ProxOperator": r"""Chain @@ -336,7 +338,8 @@ def __init__(self, f: ProxOperator, v: NDArray) -> None: # if not isinstance(f, ProxOperator): # raise ValueError('First input must be a ProxOperator') if not isinstance(v, (np.ndarray, cp_dtype)): - raise ValueError("Second input must be a numpy.ndarray or cupy.ndarray") + msg = "Second input must be a numpy.ndarray or cupy.ndarray" + raise ValueError(msg) self.f, self.v = f, v super().__init__(None, f.hasgrad) @@ -375,7 +378,8 @@ def __init__(self, f: ProxOperator, sigma: float) -> None: # if not isinstance(f, ProxOperator): # raise ValueError('First input must be a ProxOperator') if not isinstance(sigma, float): - raise ValueError("Second input must be a float") + msg = "Second input must be a float" + raise ValueError(msg) self.f, self.sigma = f, sigma super().__init__(None, f.hasgrad) @@ -395,11 +399,11 @@ def __init__(self, f: ProxOperator, a: float, b: float | NDArray) -> None: # if not isinstance(f, ProxOperator): # raise ValueError('First input must be a ProxOperator') if not isinstance(a, float): - raise ValueError("Second input must be a float") + msg = "Second input must be a float" + raise ValueError(msg) if not isinstance(b, (float, np.ndarray, cp_dtype)): - raise ValueError( - "Third input must be a float, numpy.ndarray, or cupy.ndarray" - ) + msg = "Third input must be a float, numpy.ndarray, or cupy.ndarray" + raise ValueError(msg) self.f, self.a, self.b = f, a, b super().__init__(None, f.hasgrad) diff --git a/pyproximal/optimization/bregman.py b/pyproximal/optimization/bregman.py index cad51de..54fa2e7 100644 --- a/pyproximal/optimization/bregman.py +++ b/pyproximal/optimization/bregman.py @@ -1,6 +1,7 @@ import time from copy import deepcopy -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional +from typing import TYPE_CHECKING, Any, Optional +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -22,9 +23,9 @@ def Bregman( warm: bool = False, tolx: float = 1e-10, tolf: float = 1e-10, - bregcallback: Optional[Callable[[NDArray], None]] = None, + bregcallback: Callable[[NDArray], None] | None = None, show: bool = False, - **kwargs_solver: Dict[str, Any], + **kwargs_solver: dict[str, Any], ) -> NDArray: r"""Bregman iterations with Proximal Solver diff --git a/pyproximal/optimization/palm.py b/pyproximal/optimization/palm.py index b15e75c..a8bca32 100644 --- a/pyproximal/optimization/palm.py +++ b/pyproximal/optimization/palm.py @@ -1,5 +1,5 @@ import time -from typing import Callable, List, Optional, Tuple +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -9,14 +9,14 @@ def _backtracking( - x: List[NDArray], + x: list[NDArray], tau: float, H: BilinearOperator, - proxf: Optional[ProxOperator], + proxf: ProxOperator | None, ix: int, beta: float = 0.5, niterback: int = 10, -) -> Tuple[NDArray, float]: +) -> tuple[NDArray, float]: r"""Backtracking Line-search algorithm for finding step sizes in PALM algorithms when @@ -27,7 +27,7 @@ def _backtracking( def ftilde( x: NDArray, - y: List[NDArray], + y: list[NDArray], f: BilinearOperator, g: NDArray, tau: float, @@ -59,18 +59,18 @@ def ftilde( def PALM( H: BilinearOperator, - proxf: Optional[ProxOperator], - proxg: Optional[ProxOperator], + proxf: ProxOperator | None, + proxg: ProxOperator | None, x0: NDArray, y0: NDArray, - gammaf: Optional[float] = 1.0, - gammag: Optional[float] = 1.0, + gammaf: float | None = 1.0, + gammag: float | None = 1.0, beta: float = 0.5, niter: int = 10, niterback: int = 100, - callback: Optional[Callable[[NDArray, NDArray], None]] = None, + callback: Callable[[NDArray, NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Proximal Alternating Linearized Minimization Solves the following minimization problem using the Proximal Alternating @@ -220,19 +220,19 @@ def PALM( def iPALM( H: BilinearOperator, - proxf: Optional[ProxOperator], - proxg: Optional[ProxOperator], + proxf: ProxOperator | None, + proxg: ProxOperator | None, x0: NDArray, y0: NDArray, - gammaf: Optional[float] = 1.0, - gammag: Optional[float] = 1.0, - a: List[float] = [1.0, 1.0], + gammaf: float | None = 1.0, + gammag: float | None = 1.0, + a: tuple[float, float] = (1.0, 1.0), beta: float = 0.5, niter: int = 10, niterback: int = 100, - callback: Optional[Callable[[NDArray, NDArray], None]] = None, + callback: Callable[[NDArray, NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Inertial Proximal Alternating Linearized Minimization Solves the following minimization problem using the Inertial Proximal diff --git a/pyproximal/optimization/pnp.py b/pyproximal/optimization/pnp.py index bf658f9..bba884d 100644 --- a/pyproximal/optimization/pnp.py +++ b/pyproximal/optimization/pnp.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict, Tuple, Union +from typing import Any +from collections.abc import Callable from pylops.utils.typing import NDArray, ShapeLike @@ -47,8 +48,8 @@ def PlugAndPlay( dims: ShapeLike, x0: NDArray, solver: Callable[..., NDArray] = ADMM, - **kwargs_solver: Dict[str, Any], -) -> Union[NDArray, Tuple[NDArray, ...]]: + **kwargs_solver: dict[str, Any], +) -> NDArray | tuple[NDArray, ...]: r"""Plug-and-Play Priors with any proximal algorithm of choice Solves the following minimization problem using any proximal a diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index 423707a..b85d78b 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -1,7 +1,8 @@ import time import warnings from math import sqrt -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Optional +from collections.abc import Callable import numpy as np from pylops.optimization.leastsquares import regularized_inversion @@ -24,7 +25,7 @@ def _backtracking( epsg: float, beta: float = 0.5, niterback: int = 10, -) -> Tuple[NDArray, float]: +) -> tuple[NDArray, float]: r"""Backtracking Line-search algorithm for finding step sizes in proximal algorithms when @@ -54,9 +55,9 @@ def _x0z0_init( x0: NDArray | None, z0: NDArray | None, Op: Optional["LinearOperator"] = None, - z0name: Optional[str] = "z0", - Opname: Optional[str] = "Op", -) -> Tuple[NDArray, NDArray]: + z0name: str | None = "z0", + Opname: str | None = "Op", +) -> tuple[NDArray, NDArray]: r"""Initialize x0 and z0 Initialize x0 and z0 using the following convention. @@ -85,9 +86,8 @@ def _x0z0_init( """ if x0 is None and z0 is None: - raise ValueError( - f"Both x0 or {z0name} are None, provide either of them or both" - ) + msg = f"Both x0 or {z0name} are None, provide either of them or both" + raise ValueError(msg) if Op is None: if x0 is None: @@ -96,7 +96,8 @@ def _x0z0_init( z0 = x0.copy() else: if x0 is None: - raise ValueError(f"x0 must be provided when {Opname} is also provided") + msg = f"x0 must be provided when {Opname} is also provided" + raise ValueError(msg) elif z0 is None: z0 = Op @ x0 return x0, z0 @@ -107,8 +108,8 @@ def ProximalPoint( x0: NDArray, tau: float, niter: int = 10, - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Proximal point algorithm @@ -209,16 +210,16 @@ def ProximalGradient( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - epsg: Union[float, NDArray] = 1.0, - tau: Optional[float] = None, + epsg: float | NDArray = 1.0, + tau: float | None = None, backtracking: bool = False, beta: float = 0.5, eta: float = 1.0, niter: int = 10, niterback: int = 100, - acceleration: Optional[str] = None, - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + acceleration: str | None = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Proximal gradient (optionally accelerated) @@ -329,9 +330,8 @@ def ProximalGradient( epsg_print = "Multi" if acceleration not in [None, "None", "vandenberghe", "fista"]: - raise NotImplementedError( - "Acceleration should be None, vandenberghe " "or fista" - ) + msg = "Acceleration should be None, vandenberghe or fista" + raise NotImplementedError(msg) if show: tstart = time.time() print( @@ -453,14 +453,14 @@ def AcceleratedProximalGradient( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - tau: Optional[float] = None, + tau: float | None = None, beta: float = 0.5, epsg: float = 1.0, niter: int = 10, niterback: int = 100, acceleration: str = "vandenberghe", - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Accelerated Proximal gradient @@ -476,6 +476,7 @@ def AcceleratedProximalGradient( "appropriate acceleration parameter as this behaviour will become default in " "version v1.0.0 and AcceleratedProximalGradient will be removed.", FutureWarning, + stacklevel=2, ) return ProximalGradient( proxf, @@ -497,14 +498,14 @@ def AndersonProximalGradient( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - epsg: Union[float, NDArray] = 1.0, - tau: Union[float, NDArray] = 1.0, + epsg: float | NDArray = 1.0, + tau: float | NDArray = 1.0, niter: int = 10, nhistory: int = 10, epsr: float = 1e-10, safeguard: bool = False, - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Proximal gradient with Anderson acceleration @@ -623,18 +624,20 @@ def AndersonProximalGradient( x = proxg.prox(y, epsg[0] * tau) g = y.copy() r = g - x0 - R, G = [ - g, - ], [ - r, - ] + R, G = ( + [ + g, + ], + [ + r, + ], + ) pf = proxf(x) pfg = np.inf tolbreak = False # iterate for iiter in range(niter): - # update fix point g = x - tau * proxf.grad(x) r = g - y @@ -720,16 +723,16 @@ def AndersonProximalGradient( def GeneralizedProximalGradient( - proxfs: List[ProxOperator], - proxgs: List[ProxOperator], + proxfs: list[ProxOperator], + proxgs: list[ProxOperator], x0: NDArray, - tau: Optional[float], - epsg: Union[float, NDArray] = 1.0, - weights: Optional[NDArray] = None, + tau: float | None, + epsg: float | NDArray = 1.0, + weights: NDArray | None = None, eta: float = 1.0, niter: int = 10, - acceleration: Optional[str] = None, - callback: Optional[Callable[[NDArray], None]] = None, + acceleration: str | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Generalized Proximal gradient @@ -803,9 +806,8 @@ def GeneralizedProximalGradient( if weights is None: weights = np.ones(len(proxgs)) / len(proxgs) if len(weights) != len(proxgs) or np.sum(weights) != 1.0: - raise ValueError( - f"omega={weights} must be an array of size {len(proxgs)} " f"summing to 1" - ) + msg = f"omega={weights} must be an array of size {len(proxgs)} summing to 1" + raise ValueError(msg) # check if epgs is a vector epsg = np.asarray(epsg, dtype=float) @@ -816,9 +818,8 @@ def GeneralizedProximalGradient( epsg_print = "Multi" if acceleration not in [None, "None", "vandenberghe", "fista"]: - raise NotImplementedError( - "Acceleration should be None, vandenberghe " "or fista" - ) + msg = "Acceleration should be None, vandenberghe or fista" + raise NotImplementedError(msg) if show: tstart = time.time() print( @@ -853,7 +854,7 @@ def GeneralizedProximalGradient( # gradient grad = np.zeros_like(x) - for i, proxf in enumerate(proxfs): + for _, proxf in enumerate(proxfs): grad += proxf.grad(x) # proximal step @@ -882,7 +883,9 @@ def GeneralizedProximalGradient( if show: if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: pf: float = np.sum([proxf(x) for proxf in proxfs]) - pg: float = np.sum([eg * proxg(x) for proxg, eg in zip(proxgs, epsg)]) + pg: float = np.sum( + [eg * proxg(x) for proxg, eg in zip(proxgs, epsg, strict=True)] + ) msg = "%6g %12.5e %10.3e %10.3e %10.3e" % ( iiter + 1, x[0] if x.ndim == 1 else x[0, 0], @@ -901,14 +904,14 @@ def HQS( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - tau: Union[float, NDArray], + tau: float | NDArray, niter: int = 10, - z0: Optional[NDArray] = None, + z0: NDArray | None = None, gfirst: bool = True, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbackz: bool = False, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Half Quadratic splitting Solves the following minimization problem using Half Quadratic splitting @@ -1054,12 +1057,12 @@ def ADMM( x0: NDArray, tau: float, niter: int = 10, - z0: Optional[NDArray] = None, + z0: NDArray | None = None, gfirst: bool = False, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbackz: bool = False, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Alternating Direction Method of Multipliers Solves the following minimization problem using Alternating Direction @@ -1213,12 +1216,12 @@ def ADMML2( x0: NDArray, tau: float, niter: int = 10, - z0: Optional[NDArray] = None, + z0: NDArray | None = None, gfirst: bool = False, - callback: Optional[Callable[[NDArray], None]] = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, - **kwargs_solver: Dict[str, Any], -) -> Tuple[NDArray, NDArray]: + **kwargs_solver: dict[str, Any], +) -> tuple[NDArray, NDArray]: r"""Alternating Direction Method of Multipliers for L2 misfit term Solves the following minimization problem using Alternating Direction @@ -1381,10 +1384,10 @@ def LinearizedADMM( tau: float, mu: float, niter: int = 10, - z0: Optional[NDArray] = None, - callback: Optional[Callable[[NDArray], None]] = None, + z0: NDArray | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Linearized Alternating Direction Method of Multipliers Solves the following minimization problem using Linearized Alternating @@ -1513,14 +1516,14 @@ def TwIST( A: "LinearOperator", b: NDArray, x0: NDArray, - alpha: Optional[float] = None, - beta: Optional[float] = None, - eigs: Optional[Tuple[float, float]] = None, + alpha: float | None = None, + beta: float | None = None, + eigs: tuple[float, float] | None = None, niter: int = 10, - callback: Optional[Callable[[NDArray], None]] = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, returncost: bool = False, -) -> Union[NDArray, Tuple[NDArray, NDArray]]: +) -> NDArray | tuple[NDArray, NDArray]: r"""Two-step Iterative Shrinkage/Threshold Solves the following minimization problem using Two-step Iterative @@ -1685,10 +1688,10 @@ def DouglasRachfordSplitting( eta: float = 1.0, niter: int = 10, gfirst: bool = True, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbacky: bool = False, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Douglas-Rachford Splitting Solves the following minimization problem using Douglas-Rachford Splitting @@ -1770,7 +1773,6 @@ def DouglasRachfordSplitting( x = x0.copy() for iiter in range(niter): - if gfirst: y = proxg.prox(x, tau) x = x + eta * (proxf.prox(2 * y - x, tau) - y) @@ -1799,14 +1801,14 @@ def DouglasRachfordSplitting( def PPXA( # pylint: disable=invalid-name - proxfs: List[ProxOperator], - x0: NDArray | List[NDArray], + proxfs: list[ProxOperator], + x0: NDArray | list[NDArray], tau: float, eta: float = 1.0, - weights: NDArray | List[float] | None = None, + weights: NDArray | list[float] | None = None, niter: int = 1000, - tol: Optional[float] = 1e-7, - callback: Optional[Callable[..., None]] = None, + tol: float | None = 1e-7, + callback: Callable[..., None] | None = None, show: bool = False, ) -> NDArray: r"""Parallel Proximal Algorithm (PPXA) @@ -1932,7 +1934,6 @@ def PPXA( # pylint: disable=invalid-name # iterate for iiter in range(niter): - p = ncp.stack([proxfs[i].prox(y[i], tau / w[i]) for i in range(m)]) pn = ncp.sum(w[:, None] * p, axis=0) y = y + eta * (2 * pn - x - p) @@ -1946,9 +1947,7 @@ def PPXA( # pylint: disable=invalid-name if show: if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: pf = ncp.sum([proxfs[i](x) for i in range(m)]) - print( - f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} " f"{pf:10.3e}" - ) + print(f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} {pf:10.3e}") # break if tolerance condition is met if ncp.abs(x - x_old).max() < tol: @@ -1964,12 +1963,12 @@ def PPXA( # pylint: disable=invalid-name def ConsensusADMM( # pylint: disable=invalid-name - proxfs: List[ProxOperator], + proxfs: list[ProxOperator], x0: NDArray, tau: float, niter: int = 1000, - tol: Optional[float] = 1e-7, - callback: Optional[Callable[..., None]] = None, + tol: float | None = 1e-7, + callback: Callable[..., None] | None = None, show: bool = False, ) -> NDArray: r"""Consensus ADMM @@ -2050,8 +2049,7 @@ def ConsensusADMM( # pylint: disable=invalid-name if show: tstart = time.time() print( - "Consensus ADMM\n" - "---------------------------------------------------------" + "Consensus ADMM\n---------------------------------------------------------" ) for i, proxf in enumerate(proxfs): print(f"Proximal operator (f{i}): {type(proxf)}") @@ -2069,7 +2067,6 @@ def ConsensusADMM( # pylint: disable=invalid-name # iterate for iiter in range(niter): - x = ncp.stack([proxfs[i].prox(x_bar - y[i], tau) for i in range(m)]) x_bar = ncp.mean(x, axis=0) y = y + x - x_bar @@ -2083,8 +2080,7 @@ def ConsensusADMM( # pylint: disable=invalid-name if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: pf = ncp.sum([proxfs[i](x_bar) for i in range(m)]) print( - f"{iiter + 1:6d} {ncp.real(to_numpy(x_bar[0])):12.5e} " - f"{pf:10.3e}" + f"{iiter + 1:6d} {ncp.real(to_numpy(x_bar[0])):12.5e} {pf:10.3e}" ) # break if tolerance condition is met diff --git a/pyproximal/optimization/primaldual.py b/pyproximal/optimization/primaldual.py index 4191147..5b8289d 100644 --- a/pyproximal/optimization/primaldual.py +++ b/pyproximal/optimization/primaldual.py @@ -1,5 +1,6 @@ import time -from typing import TYPE_CHECKING, Callable, Optional, Tuple, Union +from typing import TYPE_CHECKING +from collections.abc import Callable import numpy as np from pylops.utils.backend import get_array_module, to_numpy @@ -16,18 +17,18 @@ def PrimalDual( proxg: ProxOperator, A: "LinearOperator", x0: NDArray, - tau: Union[float, NDArray], - mu: Union[float, NDArray], - y0: Optional[NDArray] = None, - z: Optional[NDArray] = None, + tau: float | NDArray, + mu: float | NDArray, + y0: NDArray | None = None, + z: NDArray | None = None, theta: float = 1.0, niter: int = 10, gfirst: bool = True, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbacky: bool = False, returny: bool = False, show: bool = False, -) -> Union[NDArray, Tuple[NDArray, NDArray]]: +) -> NDArray | tuple[NDArray, NDArray]: r"""Primal-dual algorithm Solves the following (possibly) nonlinear minimization problem using @@ -231,12 +232,12 @@ def AdaptivePrimalDual( eta: float = 0.95, s: float = 1.0, delta: float = 1.5, - z: Optional[NDArray] = None, + z: NDArray | None = None, niter: int = 10, tol: float = 1e-10, - callback: Optional[Callable[[NDArray], None]] = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, Tuple[NDArray, NDArray, NDArray]]: +) -> tuple[NDArray, tuple[NDArray, NDArray, NDArray]]: r"""Adaptive Primal-dual algorithm Solves the minimization problem in @@ -351,7 +352,6 @@ def AdaptivePrimalDual( iiter = 0 while iiter < niter and p > tol and d > tol: - # store old values xold = x.copy() yold = y.copy() diff --git a/pyproximal/optimization/segmentation.py b/pyproximal/optimization/segmentation.py index 2e2ef5b..0972867 100644 --- a/pyproximal/optimization/segmentation.py +++ b/pyproximal/optimization/segmentation.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict, Optional, Tuple +from typing import Any +from collections.abc import Callable import numpy as np from pylops import BlockDiag, Gradient @@ -13,14 +14,14 @@ def Segment( cl: NDArray, sigma: float, alpha: float, - clsigmas: Optional[NDArray] = None, - z: Optional[NDArray] = None, + clsigmas: NDArray | None = None, + z: NDArray | None = None, niter: int = 10, - x0: Optional[NDArray] = None, - callback: Optional[Callable[[NDArray], None]] = None, + x0: NDArray | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, - kwargs_simplex: Optional[Dict[str, Any]] = None, -) -> Tuple[NDArray, NDArray]: + kwargs_simplex: dict[str, Any] | None = None, +) -> tuple[NDArray, NDArray]: r"""Primal-dual algorithm for image segmentation Perform image segmentation over :math:`N_{cl}` classes using the diff --git a/pyproximal/optimization/sr3.py b/pyproximal/optimization/sr3.py index 25e4925..cf97bfd 100644 --- a/pyproximal/optimization/sr3.py +++ b/pyproximal/optimization/sr3.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np import pylops @@ -98,7 +98,7 @@ def SR3( data: NDArray, kappa: float, eps: float, - x0: Optional[NDArray] = None, + x0: NDArray | None = None, adaptive: bool = True, iter_lim_outer: int = 100, iter_lim_inner: int = 100, diff --git a/pyproximal/projection/Box.py b/pyproximal/projection/Box.py index 6534c1e..38e0f01 100644 --- a/pyproximal/projection/Box.py +++ b/pyproximal/projection/Box.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray from scipy.optimize import bisect @@ -41,8 +39,8 @@ class BoxProj: def __init__( self, - lower: Union[float, NDArray] = -np.inf, - upper: Union[float, NDArray] = np.inf, + lower: float | NDArray = -np.inf, + upper: float | NDArray = np.inf, ) -> None: self.lower = lower self.upper = upper @@ -107,8 +105,8 @@ def __init__( self, coeffs: NDArray, scalar: float, - lower: Union[float, NDArray] = -np.inf, - upper: Union[float, NDArray] = np.inf, + lower: float | NDArray = -np.inf, + upper: float | NDArray = np.inf, maxiter: int = 100, xtol: float = 1e-5, ) -> None: diff --git a/pyproximal/projection/Euclidean.py b/pyproximal/projection/Euclidean.py index 6161999..001a9b7 100644 --- a/pyproximal/projection/Euclidean.py +++ b/pyproximal/projection/Euclidean.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -34,7 +32,7 @@ class EuclideanBallProj: """ - def __init__(self, center: Union[NDArray, float], radius: float): + def __init__(self, center: NDArray | float, radius: float): self.center = center self.radius = radius diff --git a/pyproximal/projection/GenericIntersection.py b/pyproximal/projection/GenericIntersection.py index 8d4cd27..623cb7e 100644 --- a/pyproximal/projection/GenericIntersection.py +++ b/pyproximal/projection/GenericIntersection.py @@ -1,4 +1,4 @@ -from typing import Callable, List +from collections.abc import Callable from pylops.utils.typing import NDArray @@ -100,12 +100,11 @@ class GenericIntersectionProj: def __init__( self, - projections: List[Callable[[NDArray], NDArray]], + projections: list[Callable[[NDArray], NDArray]], niter: int = 1000, tol: float = 1e-6, use_parallel: bool = False, ) -> None: - self.projections = projections self.niter = niter self.tol = tol @@ -125,14 +124,16 @@ def __call__(self, x: NDArray) -> NDArray: def _single_proj(self, x0: NDArray) -> NDArray: r"""Compute projection :math:`P_C(\mathbf{x})` for :math:`m=1`.""" if len(self.projections) != 1: - raise ValueError("len(projections) should be 1") + msg = "len(projections) should be 1" + raise ValueError(msg) return self.projections[0](x0) def _two_proj(self, x0: NDArray) -> NDArray: r"""Compute projection :math:`P_C(\mathbf{x})` for :math:`m=2`.""" if len(self.projections) != 2: - raise ValueError("len(projections) should be 2") + msg = "len(projections) should be 2" + raise ValueError(msg) step1, step2 = self.projections @@ -147,7 +148,8 @@ def _two_proj(self, x0: NDArray) -> NDArray: def _more_proj(self, x0: NDArray) -> NDArray: r"""Compute projection :math:`P_C(x)` for :math:`m \ge 2`.""" if len(self.projections) < 2: - raise ValueError("len(projections) should be 2 or larger") + msg = "len(projections) should be 2 or larger" + raise ValueError(msg) return parallel_dykstra_projection( x0, diff --git a/pyproximal/projection/Intersection.py b/pyproximal/projection/Intersection.py index 9a886e8..30160d3 100644 --- a/pyproximal/projection/Intersection.py +++ b/pyproximal/projection/Intersection.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -41,7 +39,7 @@ def __init__( self, k: int, n: int, - sigma: Union[float, NDArray], + sigma: float | NDArray, niter: int = 100, tol: float = 1e-5, ) -> None: @@ -56,7 +54,7 @@ def __init__( def __call__(self, x: NDArray) -> NDArray: x = x.reshape(self.k, self.n) x12 = np.zeros((self.k, self.k, self.n)) - for iiter in range(self.niter): + for _ in range(self.niter): xold = x.copy() for i1 in range(self.k - 1): for i2 in range(i1 + 1, self.k): diff --git a/pyproximal/projection/L0.py b/pyproximal/projection/L0.py index a78a206..e470d28 100644 --- a/pyproximal/projection/L0.py +++ b/pyproximal/projection/L0.py @@ -77,10 +77,14 @@ def __call__(self, x: NDArray) -> NDArray: class L01BallProj(L10BallProj): def __init__(self, radius: int) -> None: - warnings.warn( + msg = ( "The L01BallProj class has been renamed L10BallProj due " "to a mistake in the original choice of the name. As such " - "L01BallProj will be deprecated in v1.0.0.", + "L01BallProj will be deprecated in v1.0.0." + ) + warnings.warn( + msg, FutureWarning, + stacklevel=2, ) super().__init__(radius) diff --git a/pyproximal/proximal/Box.py b/pyproximal/proximal/Box.py index 7adc82d..ae56271 100644 --- a/pyproximal/proximal/Box.py +++ b/pyproximal/proximal/Box.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -29,8 +27,8 @@ class Box(ProxOperator): def __init__( self, - lower: Union[float, NDArray] = -np.inf, - upper: Union[float, NDArray] = np.inf, + lower: float | NDArray = -np.inf, + upper: float | NDArray = np.inf, ) -> None: super().__init__(None, False) self.lower = lower diff --git a/pyproximal/proximal/ETP.py b/pyproximal/proximal/ETP.py index 0b9be0a..8643114 100644 --- a/pyproximal/proximal/ETP.py +++ b/pyproximal/proximal/ETP.py @@ -51,9 +51,11 @@ class ETP(ProxOperator): def __init__(self, sigma: float, gamma: float = 1.0) -> None: super().__init__(None, False) if sigma < 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if gamma <= 0: - raise ValueError('Variable "gamma" must be strictly positive.') + msg = 'Variable "gamma" must be strictly positive.' + raise ValueError(msg) self.sigma = sigma self.gamma = gamma diff --git a/pyproximal/proximal/Euclidean.py b/pyproximal/proximal/Euclidean.py index fd319bb..c068601 100644 --- a/pyproximal/proximal/Euclidean.py +++ b/pyproximal/proximal/Euclidean.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -85,7 +83,7 @@ class EuclideanBall(ProxOperator): """ - def __init__(self, center: Union[NDArray, float], radius: float) -> None: + def __init__(self, center: NDArray | float, radius: float) -> None: super().__init__(None, False) self.center = center self.radius = radius diff --git a/pyproximal/proximal/Geman.py b/pyproximal/proximal/Geman.py index aeac30e..371591d 100644 --- a/pyproximal/proximal/Geman.py +++ b/pyproximal/proximal/Geman.py @@ -1,5 +1,3 @@ -from typing import Tuple - import numpy as np from pylops.utils.typing import NDArray @@ -50,9 +48,11 @@ class Geman(ProxOperator): def __init__(self, sigma: float, gamma: float = 1.3) -> None: super().__init__(None, False) if sigma < 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if gamma <= 0: - raise ValueError('Variable "gamma" must be strictly positive.') + msg = 'Variable "gamma" must be strictly positive.' + raise ValueError(msg) self.sigma = sigma self.gamma = gamma @@ -80,7 +80,7 @@ def prox(self, x: NDArray, tau: float) -> NDArray: @staticmethod def _find_local_minima( b: NDArray, c: NDArray, d: NDArray - ) -> Tuple[NDArray, NDArray]: + ) -> tuple[NDArray, NDArray]: f = -((c - b**2.0 / 3.0) ** 3.0) / 27.0 g = (2.0 * b**3.0 - 9.0 * b * c + 27.0 * d) / 27.0 idx = g**2.0 / 4.0 - f <= 0 diff --git a/pyproximal/proximal/GenericIntersection.py b/pyproximal/proximal/GenericIntersection.py index 7973aa7..ef59520 100644 --- a/pyproximal/proximal/GenericIntersection.py +++ b/pyproximal/proximal/GenericIntersection.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, List +from typing import Any +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -38,7 +39,7 @@ class GenericIntersectionProx(ProxOperator): def __init__( self, - projections: List[Callable[[NDArray], NDArray]], + projections: list[Callable[[NDArray], NDArray]], niter: int = 1000, tol: float = 1e-6, use_parallel: bool = False, diff --git a/pyproximal/proximal/Intersection.py b/pyproximal/proximal/Intersection.py index d417035..4bf74a1 100644 --- a/pyproximal/proximal/Intersection.py +++ b/pyproximal/proximal/Intersection.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -38,7 +36,7 @@ def __init__( self, k: int, n: int, - sigma: Union[float, NDArray], + sigma: float | NDArray, niter: int = 100, tol: float = 1e-5, call: bool = True, diff --git a/pyproximal/proximal/L0.py b/pyproximal/proximal/L0.py index 8a94f39..710cf04 100644 --- a/pyproximal/proximal/L0.py +++ b/pyproximal/proximal/L0.py @@ -1,5 +1,6 @@ import warnings -from typing import Any, Callable, Union +from typing import Any +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -42,7 +43,7 @@ def _hardthreshold(x: NDArray, thresh: float) -> NDArray: def _current_radius( radius: IntCallableLike, count: int, -) -> Union[int, NDArray]: +) -> int | NDArray: if not callable(radius): return radius else: @@ -233,5 +234,6 @@ def __init__(self, ndim: int, radius: IntCallableLike) -> None: "to a mistake in the original choice of the name. As such " "L01Ball will be deprecated in v1.0.0.", FutureWarning, + stacklevel=2, ) super().__init__(ndim, radius) diff --git a/pyproximal/proximal/L1.py b/pyproximal/proximal/L1.py index 6fbf6bc..b2e3530 100644 --- a/pyproximal/proximal/L1.py +++ b/pyproximal/proximal/L1.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional, Union +from typing import Any +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -40,7 +41,7 @@ def _softthreshold(x: NDArray, thresh: float) -> NDArray: def _current_sigma( sigma: FloatCallableLike, count: int, -) -> Union[float, NDArray]: +) -> float | NDArray: if not callable(sigma): return sigma else: @@ -106,7 +107,7 @@ class L1(ProxOperator): def __init__( self, sigma: FloatCallableLike = 1.0, - g: Optional[NDArray] = None, + g: NDArray | None = None, ) -> None: super().__init__(None, False) self.sigma = sigma diff --git a/pyproximal/proximal/L2.py b/pyproximal/proximal/L2.py index 8084bc7..8d1b1a7 100644 --- a/pyproximal/proximal/L2.py +++ b/pyproximal/proximal/L2.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Optional +from collections.abc import Callable import numpy as np from pylops import Identity, MatrixMult @@ -132,17 +133,17 @@ class L2(ProxOperator): def __init__( self, Op: Optional["LinearOperator"] = None, - b: Optional[NDArray] = None, - q: Optional[NDArray] = None, + b: NDArray | None = None, + q: NDArray | None = None, sigma: float = 1.0, alpha: float = 1.0, qgrad: bool = True, - niter: Union[int, Callable[[int], int]] = 10, - x0: Optional[NDArray] = None, + niter: int | Callable[[int], int] = 10, + x0: NDArray | None = None, warm: bool = True, - solver: Optional[str] = "legacy", - densesolver: Optional[str] = None, - kwargs_solver: Optional[Dict[str, Any]] = None, + solver: str | None = "legacy", + densesolver: str | None = None, + kwargs_solver: dict[str, Any] | None = None, ) -> None: super().__init__(Op, True) self.b = b @@ -165,10 +166,11 @@ def __init__( elif self.solver == "cgls": self.normaleqs = False else: - raise ValueError( + msg = ( f"Provided solver={self.solver}. " "Available options are: 'legacy', 'cg', 'cgls'." ) + raise ValueError(msg) # when using factorize, store the first tau*sigma=0 so that the # first time it will be recomputed (as tau cannot be 0) if self.densesolver == "factorize": @@ -342,8 +344,8 @@ def __init__( b: NDArray, nfft: int = 2**10, sigma: float = 1.0, - dims: Optional[ShapeLike] = None, - dir: Optional[int] = None, + dims: ShapeLike | None = None, + dir: int | None = None, ) -> None: super().__init__(None, True) self.nfft = nfft diff --git a/pyproximal/proximal/Log.py b/pyproximal/proximal/Log.py index 8f38640..5a9d10b 100644 --- a/pyproximal/proximal/Log.py +++ b/pyproximal/proximal/Log.py @@ -73,9 +73,11 @@ class Log(ProxOperator): def __init__(self, sigma: float, gamma: float = 1.3) -> None: super().__init__(None, False) if sigma < 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if gamma < 0: - raise ValueError('Variable "gamma" must be positive.') + msg = 'Variable "gamma" must be positive.' + raise ValueError(msg) self.sigma = sigma self.gamma = gamma @@ -146,7 +148,8 @@ class Log1(ProxOperator): def __init__(self, sigma: float, delta: float = 1e-10) -> None: super().__init__(None, False) if delta < 0: - raise ValueError('Variable "delta" must be positive.') + msg = 'Variable "delta" must be positive.' + raise ValueError(msg) self.sigma = sigma self.delta = delta diff --git a/pyproximal/proximal/Nonlinear.py b/pyproximal/proximal/Nonlinear.py index 82ac4e8..eef13cd 100644 --- a/pyproximal/proximal/Nonlinear.py +++ b/pyproximal/proximal/Nonlinear.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import Tuple from pylops.utils.typing import NDArray @@ -84,13 +83,13 @@ def _funprox(self, x: NDArray, tau: float) -> float: def _gradprox(self, x: NDArray, tau: float) -> NDArray: return self.grad(x) + 1.0 / tau * (x - self.y) - def _fungradprox(self, x: NDArray, tau: float) -> Tuple[float, NDArray]: + def _fungradprox(self, x: NDArray, tau: float) -> tuple[float, NDArray]: f, g = self.fungrad(x) f = f + 1.0 / (2 * tau) * ((x - self.y) ** 2).sum() g = g + 1.0 / tau * (x - self.y) return f, g - def fungrad(self, x: NDArray) -> Tuple[float, NDArray]: + def fungrad(self, x: NDArray) -> tuple[float, NDArray]: f = self.fun(x) g = self.grad(x) return f, g diff --git a/pyproximal/proximal/Nuclear.py b/pyproximal/proximal/Nuclear.py index a4227ed..149c5c0 100644 --- a/pyproximal/proximal/Nuclear.py +++ b/pyproximal/proximal/Nuclear.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.optimization.cls_sparsity import _softthreshold from pylops.utils.typing import NDArray, ShapeLike @@ -57,7 +55,7 @@ class Nuclear(ProxOperator): """ - def __init__(self, dim: ShapeLike, sigma: Union[float, NDArray] = 1.0) -> None: + def __init__(self, dim: ShapeLike, sigma: float | NDArray = 1.0) -> None: super().__init__(None, False) self.dim = dim self.sigma = sigma diff --git a/pyproximal/proximal/Orthogonal.py b/pyproximal/proximal/Orthogonal.py index c9b2a40..479860b 100644 --- a/pyproximal/proximal/Orthogonal.py +++ b/pyproximal/proximal/Orthogonal.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from pylops.utils.typing import NDArray @@ -65,7 +65,7 @@ def __init__( f: ProxOperator, Q: "LinearOperator", partial: bool = False, - b: Optional[NDArray] = None, + b: NDArray | None = None, alpha: float = 1.0, ) -> None: super().__init__(None, False) diff --git a/pyproximal/proximal/Quadratic.py b/pyproximal/proximal/Quadratic.py index 3631886..8a6ebfd 100644 --- a/pyproximal/proximal/Quadratic.py +++ b/pyproximal/proximal/Quadratic.py @@ -72,15 +72,16 @@ class Quadratic(ProxOperator): def __init__( self, Op: Optional["LinearOperator"] = None, - b: Optional[NDArray] = None, + b: NDArray | None = None, c: float = 0.0, niter: int = 10, - x0: Optional[NDArray] = None, + x0: NDArray | None = None, warm: bool = True, ) -> None: if Op is not None: if Op.shape[0] != Op.shape[1]: - raise ValueError("Op must be square") + msg = "Op must be square" + raise ValueError(msg) super().__init__(Op, True) self.b = b if self.Op is not None and self.b is None: diff --git a/pyproximal/proximal/QuadraticEnvelope.py b/pyproximal/proximal/QuadraticEnvelope.py index 5463942..a07ee16 100644 --- a/pyproximal/proximal/QuadraticEnvelope.py +++ b/pyproximal/proximal/QuadraticEnvelope.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.optimization.cls_sparsity import _hardthreshold from pylops.utils.typing import IntNDArray, NDArray, ShapeLike @@ -165,7 +163,7 @@ class QuadraticEnvelopeCardIndicator(ProxOperator): """ - def __init__(self, r0: Union[int, IntNDArray]) -> None: + def __init__(self, r0: int | IntNDArray) -> None: super().__init__(None, False) self.r0 = r0 @@ -311,7 +309,6 @@ def prox(self, x: NDArray, tau: float) -> NDArray: zk[self.r0 :] = (1 + rho) * yk[self.r0 :] for k, ii in enumerate(ind): - if ii < self.r0: a = a + (rho + 1) / rho b = b + (rho + 1) / rho * yk[ii] diff --git a/pyproximal/proximal/RelaxedMS.py b/pyproximal/proximal/RelaxedMS.py index 588a81f..1d7eea9 100644 --- a/pyproximal/proximal/RelaxedMS.py +++ b/pyproximal/proximal/RelaxedMS.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Union +from typing import Any +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -34,7 +35,7 @@ def _l2(x: NDArray, alpha: float) -> NDArray: def _current_kappa( kappa: FloatCallableLike, count: int, -) -> Union[float, NDArray]: +) -> float | NDArray: if not callable(kappa): return kappa else: diff --git a/pyproximal/proximal/SCAD.py b/pyproximal/proximal/SCAD.py index acef8b2..9e7ef77 100644 --- a/pyproximal/proximal/SCAD.py +++ b/pyproximal/proximal/SCAD.py @@ -53,9 +53,11 @@ def __init__(self, sigma: float, a: float = 3.7) -> None: super().__init__(None, False) self.sigma = sigma if sigma <= 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if a <= 2: - raise ValueError('Variable "a" must be larger than two.') + msg = 'Variable "a" must be larger than two.' + raise ValueError(msg) self.a = a def __call__(self, x: NDArray) -> float: @@ -68,7 +70,7 @@ def elementwise(self, x: NDArray) -> NDArray: f[ind] = self.sigma * absx[ind] ind = np.logical_and(self.sigma < absx, absx <= self.a * self.sigma) f[ind] = ( - -x[ind] ** 2 + 2 * self.a * self.sigma * absx[ind] - self.sigma**2 + -(x[ind] ** 2) + 2 * self.a * self.sigma * absx[ind] - self.sigma**2 ) / (2 * (self.a - 1)) ind = absx > self.a * self.sigma f[ind] = (self.a + 1) * self.sigma**2 / 2 diff --git a/pyproximal/proximal/Simplex.py b/pyproximal/proximal/Simplex.py index 60809cd..a6c40dd 100644 --- a/pyproximal/proximal/Simplex.py +++ b/pyproximal/proximal/Simplex.py @@ -1,5 +1,4 @@ import logging -from typing import Optional, Union import numpy as np from pylops.utils.backend import get_array_module, to_cupy_conditional @@ -30,7 +29,7 @@ def __init__( self, n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, xtol: float = 1e-8, @@ -38,7 +37,8 @@ def __init__( ) -> None: super().__init__(None, False) if dims is not None and len(dims) != 2: - raise ValueError("provide only 2 dimensions, or None") + msg = "provide only 2 dimensions, or None" + raise ValueError(msg) self.n = n self.radius = radius self.dims = dims @@ -96,7 +96,7 @@ def __init__( self, n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, ftol: float = 1e-8, @@ -163,7 +163,7 @@ def __init__( self, n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, ftol: float = 1e-8, @@ -208,7 +208,7 @@ def prox(self, x: NDArray, tau: float) -> NDArray: def Simplex( n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, ftol: float = 1e-8, @@ -265,9 +265,10 @@ def Simplex( """ if engine not in ["numpy", "numba", "cuda"]: - raise KeyError("engine must be numpy or numba or cuda") + msg = "engine must be numpy or numba or cuda" + raise KeyError(msg) - s: Union[_Simplex, _Simplex_numba, _Simplex_cuda] + s: _Simplex | _Simplex_numba | _Simplex_cuda if engine == "numba" and jit is not None: s = _Simplex_numba( n, diff --git a/pyproximal/proximal/Sum.py b/pyproximal/proximal/Sum.py index d78850c..f5a358c 100644 --- a/pyproximal/proximal/Sum.py +++ b/pyproximal/proximal/Sum.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, List +from typing import Any +from collections.abc import Callable from pylops.utils.backend import get_array_module from pylops.utils.typing import NDArray @@ -109,8 +110,8 @@ class Sum(ProxOperator): def __init__( self, - ops: List[ProxOperator], - weights: NDArray | List[float] | None = None, + ops: list[ProxOperator], + weights: NDArray | list[float] | None = None, niter: int = 1000, tol: float = 1e-7, use_parallel: bool = False, @@ -180,14 +181,16 @@ def prox(self, x: NDArray, tau: float, **kwargs: Any) -> NDArray: def _single_prox(self, x0: NDArray, tau: float) -> NDArray: r"""Compute :math:`\prox_{\tau \ f}(\mathbf{x})` for :math:`m = 1`.""" if len(self.ops) != 1: - raise ValueError("len(ops) should be 1") + msg = "len(ops) should be 1" + raise ValueError(msg) return self.ops[0].prox(x0, tau) def _two_prox(self, x0: NDArray, tau: float) -> NDArray: r"""Compute :math:`\prox_{\tau \ f + g}(\mathbf{x})` for :math:`m = 2`.""" if len(self.ops) != 2: - raise ValueError("len(ops) should be 2") + msg = "len(ops) should be 2" + raise ValueError(msg) def bind_tau( prox: Callable[[NDArray, float], NDArray], @@ -210,7 +213,7 @@ def _more_prox(self, x0: NDArray, tau: float) -> NDArray: for :math:`m \ge 2`. """ - def tau_policy(tau: float, w: NDArray | List[float]) -> List[float]: + def tau_policy(tau: float, w: NDArray | list[float]) -> list[float]: if self.use_original_tau: # legacy: all prox_i use the same tau return [tau] * len(w) @@ -218,7 +221,8 @@ def tau_policy(tau: float, w: NDArray | List[float]) -> List[float]: return [tau / wi for wi in w] if len(self.ops) < 2: - raise ValueError("len(ops) should be 2 or larger") + msg = "len(ops) should be 2 or larger" + raise ValueError(msg) return parallel_dykstra_prox( x0, diff --git a/pyproximal/proximal/TV.py b/pyproximal/proximal/TV.py index a3dc3ef..e9e161e 100644 --- a/pyproximal/proximal/TV.py +++ b/pyproximal/proximal/TV.py @@ -1,5 +1,6 @@ from copy import deepcopy -from typing import Any, Callable, Union +from typing import Any +from collections.abc import Callable import numpy as np from pylops import FirstDerivative, Gradient @@ -42,7 +43,7 @@ def __init__( self, dims: ShapeLike, sigma: float = 1.0, - niter: Union[int, Callable[[int], int]] = 10, + niter: int | Callable[[int], int] = 10, rtol: float = 1e-4, **kwargs: Any, ) -> None: @@ -185,7 +186,8 @@ def prox(self, x: NDArray, tau: float) -> NDArray: while iter <= niter: # Current Solution if self.ndim == 0: - raise ValueError("Need to input at least one value") + msg = "Need to input at least one value" + raise ValueError(msg) if self.ndim >= 1: div = np.concatenate( diff --git a/pyproximal/proximal/VStack.py b/pyproximal/proximal/VStack.py index 4157d84..8c3cc6a 100644 --- a/pyproximal/proximal/VStack.py +++ b/pyproximal/proximal/VStack.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from pylops.utils.typing import NDArray, ShapeLike @@ -50,8 +50,8 @@ class VStack(ProxOperator): def __init__( self, ops: list["LinearOperator"], - nn: Optional[list[ShapeLike]] = None, - restr: Optional[list["LinearOperator"]] = None, + nn: list[ShapeLike] | None = None, + restr: list["LinearOperator"] | None = None, ) -> None: super().__init__(None, any(op.hasgrad for op in ops)) self.ops = ops @@ -69,28 +69,32 @@ def __init__( # store required size of input self.nx = np.sum([restr.iava.size for restr in self.restr]) else: - raise ValueError("Provide either nn or restr") + msg = "Provide either nn or restr" + raise ValueError(msg) def __call__(self, x: NDArray) -> float: if x.size != self.nx: - raise ValueError( + msg = ( f"x must have size {self.nx}, instead the provided x has size {x.size}" ) + raise ValueError(msg) f = 0.0 if hasattr(self, "nn"): for iop, op in enumerate(self.ops): f += op(x[self.xin[iop] : self.xend[iop]]) else: - for op, restr in zip(self.ops, self.restr): + for op, restr in zip(self.ops, self.restr, strict=True): f += op(restr.matvec(x)) return float(f) @_check_tau def prox(self, x: NDArray, tau: float) -> NDArray: if x.size != self.nx: - raise ValueError( + msg = ( f"x must have size {self.nx}, instead the provided x has size {x.size}" ) + raise ValueError(msg) + if hasattr(self, "nn"): f = np.hstack( [ @@ -100,15 +104,16 @@ def prox(self, x: NDArray, tau: float) -> NDArray: ) else: f = np.zeros_like(x) - for op, restr in zip(self.ops, self.restr): + for op, restr in zip(self.ops, self.restr, strict=True): f[restr.iava] = op.prox(restr.matvec(x), tau) return f def grad(self, x: NDArray) -> NDArray: if x.size != self.nx: - raise ValueError( + msg = ( f"x must have size {self.nx}, instead the provided x has size {x.size}" ) + raise ValueError(msg) if hasattr(self, "nn"): f = np.hstack( [ @@ -118,6 +123,6 @@ def grad(self, x: NDArray) -> NDArray: ) else: f = np.zeros_like(x) - for op, restr in zip(self.ops, self.restr): + for op, restr in zip(self.ops, self.restr, strict=True): f[restr.iava] = op.grad(restr.matvec(x)) return f diff --git a/pyproximal/proximal/_Dykstra.py b/pyproximal/proximal/_Dykstra.py index 781ad77..07447d9 100644 --- a/pyproximal/proximal/_Dykstra.py +++ b/pyproximal/proximal/_Dykstra.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, List, Sequence, TypeVar +from typing import Any, TypeVar +from collections.abc import Callable, Sequence from pylops.utils.backend import get_array_module from pylops.utils.typing import NDArray @@ -20,7 +21,8 @@ def _select_impl_by_arity( ) -> ProxOpOrProj: """Choose implementation by ``len(items)`` and ``use_parallel``.""" if not items: - raise ValueError("items must not be empty") + msg = "items must not be empty" + raise ValueError(msg) if len(items) == 1: return single if len(items) == 2 and not use_parallel: @@ -59,7 +61,7 @@ def dykstra_two( def parallel_dykstra_projection( x0: NDArray, - proj_ops: List[Proj], + proj_ops: list[Proj], *, niter: int, tol: float, @@ -88,10 +90,10 @@ def parallel_dykstra_projection( def parallel_dykstra_prox( x0: NDArray, - prox_ops: List[ProxOp], + prox_ops: list[ProxOp], *, - weights: NDArray | List[float], - taus: NDArray | List[float], + weights: NDArray | list[float], + taus: NDArray | list[float], niter: int, tol: float, ) -> NDArray: diff --git a/pyproximal/proximal/_Simplex_cuda.py b/pyproximal/proximal/_Simplex_cuda.py index 6f137f6..75862fd 100644 --- a/pyproximal/proximal/_Simplex_cuda.py +++ b/pyproximal/proximal/_Simplex_cuda.py @@ -18,7 +18,7 @@ def bisect_jit_cuda( """Bisection method (See _Simplex_numba for details).""" a, b = bisect_lower, bisect_upper fa = fun_jit_cuda(a, x, coeffs, scalar, lower, upper) - for iiter in range(maxiter): + for _ in range(maxiter): c = (a + b) / 2.0 if (b - a) / 2 < xtol: return c diff --git a/pyproximal/proximal/_Simplex_numba.py b/pyproximal/proximal/_Simplex_numba.py index 26ecfbf..f2feb6a 100644 --- a/pyproximal/proximal/_Simplex_numba.py +++ b/pyproximal/proximal/_Simplex_numba.py @@ -62,7 +62,7 @@ def bisect_jit( """ a, b = bisect_lower, bisect_upper fa = fun_jit(a, x, coeffs, scalar, lower, upper) - for iiter in range(maxiter): + for _ in range(maxiter): c = (a + b) / 2.0 if (b - a) / 2 < xtol: return c diff --git a/pyproximal/utils/__init__.py b/pyproximal/utils/__init__.py index 9fd9eee..bef7422 100644 --- a/pyproximal/utils/__init__.py +++ b/pyproximal/utils/__init__.py @@ -1,4 +1,4 @@ from .backend import * -from .utils import Report from .moreau import moreau from .typing import * +from .utils import Report diff --git a/pyproximal/utils/bilinear.py b/pyproximal/utils/bilinear.py index 564e001..ed63e3f 100644 --- a/pyproximal/utils/bilinear.py +++ b/pyproximal/utils/bilinear.py @@ -48,7 +48,7 @@ def __init__(self) -> None: pass @abstractmethod - def __call__(self, x: NDArray, y: Optional[NDArray] = None) -> Any: + def __call__(self, x: NDArray, y: NDArray | None = None) -> Any: pass @abstractmethod @@ -147,7 +147,7 @@ def __init__( self.sizex = self.n * self.k self.sizey = self.m * self.k - def __call__(self, x: NDArray, y: Optional[NDArray] = None) -> float: + def __call__(self, x: NDArray, y: NDArray | None = None) -> float: # x can be concatenated [x,y] or just x if y is provided if y is None: x, y = x[: self.sizex], x[self.sizex :] @@ -185,12 +185,14 @@ def _matvecy(self, y: NDArray) -> NDArray: def matvec(self, x: NDArray) -> NDArray: # check that no ambiguous situation arises due to n==m if self.n == self.m: - raise NotImplementedError( + msg = ( "Since n=m, this method" "cannot distinguish automatically" "between _matvecx and _matvecy. " "Explicitely call either of those two methods." ) + raise NotImplementedError(msg) + if x.size == self.sizex: y = self._matvecx(x) else: @@ -201,7 +203,8 @@ def lx(self, x: NDArray) -> float: if self.Op is not None: # Lipschitz constant for grad_y H involves Op.H Op and X.H X. # This is non-trivial and depends on Op's norm. - raise ValueError("lx cannot be computed when using Op") + msg = "lx cannot be computed when using Op" + raise ValueError(msg) # if Op is None, H = 0.5 * ||XY - d||^2. grad_y H = X.H (XY-d) # Lipschitz of grad_y H involves ||X.H X||_F or ||X||_2^2 X = x.reshape(self.n, self.k) @@ -211,7 +214,8 @@ def ly(self, y: NDArray) -> float: if self.Op is not None: # Lipschitz constant for grad_x H involves Op.H Op and Y Y.H. # This is non-trivial and depends on Op's norm. - raise ValueError("ly cannot be computed when using Op") + msg = "ly cannot be computed when using Op" + raise ValueError(msg) # if Op is None, H = 0.5 * ||XY - d||^2. grad_x H = (XY-d)Y.H # Lipschitz of grad_x H involves ||Y Y.H||_F or ||Y||_2^2 Y = y.reshape(self.k, self.m) diff --git a/pyproximal/utils/gradtest.py b/pyproximal/utils/gradtest.py index fc7c26e..d3e48be 100644 --- a/pyproximal/utils/gradtest.py +++ b/pyproximal/utils/gradtest.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - import numpy as np from pylops.utils.backend import get_module from pylops.utils.typing import NDArray @@ -11,7 +9,7 @@ def gradtest_proximal( Op: ProxOperator, n: int, - x: Optional[NDArray] = None, + x: NDArray | None = None, dtype: str = "float64", delta: float = 1e-6, rtol: float = 1e-6, @@ -97,7 +95,7 @@ def gradtest_proximal( iqx = np.random.randint(0, n) r_or_i = np.random.randint(0, 2) - delta1: Union[float, complex] = delta + delta1: float | complex = delta if r_or_i != 0: delta1 = delta * 1j @@ -217,7 +215,7 @@ def gradtest_bilinear( iqx, iqy = np.random.randint(0, nx), np.random.randint(0, ny) x_or_y = np.random.randint(0, 2) - delta1: Union[float, complex] = delta + delta1: float | complex = delta if complexflag: r_or_i = np.random.randint(0, 2) if r_or_i == 1: diff --git a/pyproximal/utils/moreau.py b/pyproximal/utils/moreau.py index b230d45..c5f684f 100644 --- a/pyproximal/utils/moreau.py +++ b/pyproximal/utils/moreau.py @@ -64,5 +64,6 @@ def moreau( return True else: if raiseerror: - raise ValueError("Moreau identity not verified") + msg = "Moreau identity not verified" + raise ValueError(msg) return False diff --git a/pyproximal/utils/typing.py b/pyproximal/utils/typing.py index 5693847..05c801c 100644 --- a/pyproximal/utils/typing.py +++ b/pyproximal/utils/typing.py @@ -3,9 +3,9 @@ "IntCallableLike", ] -from typing import Callable, Union +from collections.abc import Callable from pylops.utils.typing import NDArray -FloatCallableLike = Union[float, NDArray, Callable[[int], Union[float, NDArray]]] -IntCallableLike = Union[int, Callable[[int], int]] +FloatCallableLike = float | NDArray | Callable[[int], float | NDArray] +IntCallableLike = int | Callable[[int], int] diff --git a/pyproximal/utils/utils.py b/pyproximal/utils/utils.py index b060f01..0b9bb06 100644 --- a/pyproximal/utils/utils.py +++ b/pyproximal/utils/utils.py @@ -1,7 +1,6 @@ __all__ = ["Report"] from types import ModuleType -from typing import Optional try: # scooby is a soft dependency for pyproximal @@ -11,7 +10,7 @@ class ScoobyReport: # type: ignore[no-redef] def __init__( self, - additional: Optional[list[str | ModuleType]], + additional: list[str | ModuleType] | None, core: list[str | ModuleType] | None, optional: list[str | ModuleType] | None, ncol: int, @@ -76,7 +75,7 @@ class Report(ScoobyReport): # type: ignore[misc] def __init__( self, - add_pckg: Optional[list[str | ModuleType]] = None, + add_pckg: list[str | ModuleType] | None = None, ncol: int = 3, text_width: int = 80, sort: bool = False, diff --git a/pytests/test_basic.py b/pytests/test_basic.py index 80233fa..81d130a 100644 --- a/pytests/test_basic.py +++ b/pytests/test_basic.py @@ -2,7 +2,6 @@ import pytest from numpy.testing import assert_array_equal from pylops import MatrixMult - from pyproximal.proximal import L1, L2, Euclidean, Quadratic par1 = {"ny": 21, "nx": 11, "nt": 20, "imag": 0, "dtype": "float32"} # real @@ -66,7 +65,8 @@ def test_precomposition_type(par): _ = l1.precomposition(a=1, b=np.ones(5)) # should be float _ = l1.precomposition( - a=1.0, b=[1, 2, 3] # should be float + a=1.0, + b=[1, 2, 3], # should be float ) # should be float, np.ndarray or cp.ndarray diff --git a/pytests/test_bilinear.py b/pytests/test_bilinear.py index 30c0908..4257cf2 100644 --- a/pytests/test_bilinear.py +++ b/pytests/test_bilinear.py @@ -2,7 +2,6 @@ import pytest from numpy.testing import assert_array_almost_equal from pylops import Diagonal - from pyproximal.utils.bilinear import LowRankFactorizedMatrix par1 = {"n": 21, "m": 11, "k": 5, "imag": 0, "dtype": "float32"} # real diff --git a/pytests/test_concave_penalties.py b/pytests/test_concave_penalties.py index a61dea9..864ce4c 100644 --- a/pytests/test_concave_penalties.py +++ b/pytests/test_concave_penalties.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyproximal.proximal import ETP, SCAD, Geman, Log, Log1, QuadraticEnvelopeCard from pyproximal.utils import moreau diff --git a/pytests/test_dykstra.py b/pytests/test_dykstra.py index 1b07ee0..d1d8d97 100644 --- a/pytests/test_dykstra.py +++ b/pytests/test_dykstra.py @@ -1,8 +1,8 @@ -from typing import Any, Callable, Dict +from collections.abc import Callable +from typing import Any import numpy as np import pytest - from pyproximal.projection import ( BoxProj, EuclideanBallProj, @@ -28,7 +28,7 @@ @pytest.mark.parametrize("par", [(par1proj), (par2proj)]) -def test_GenericIntersectionProx(par: Dict[str, Any]) -> None: +def test_GenericIntersectionProx(par: dict[str, Any]) -> None: """GenericIntersectionProx and proximal/dual proximal of related indicator""" rng = np.random.default_rng(10) @@ -72,11 +72,8 @@ def test_GenericIntersectionProx(par: Dict[str, Any]) -> None: [box, eucl, half_space], ] for proj in projections: - # different torelance for float32 and float64 - tol = ( - float(np.finfo(par["dtype"]).resolution) * 10.0 - ) # pylint: disable=no-member + tol = float(np.finfo(par["dtype"]).resolution) * 10.0 # pylint: disable=no-member d = GenericIntersectionProx(proj, tol=tol, niter=1000) @@ -103,7 +100,7 @@ def test_GenericIntersectionProx(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_l1_l1(par: Dict[str, Any]) -> None: +def test_Sum_l1_l1(par: dict[str, Any]) -> None: """Check Sum for L1 + L1""" atol = 1e-6 @@ -137,7 +134,7 @@ def test_Sum_l1_l1(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_l2_l2(par: Dict[str, Any]) -> None: +def test_Sum_l2_l2(par: dict[str, Any]) -> None: """Check Sum for L2 + L2""" atol = 1e-6 @@ -171,7 +168,7 @@ def test_Sum_l2_l2(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_l21_l1(par: Dict[str, Any]) -> None: +def test_Sum_l21_l1(par: dict[str, Any]) -> None: """Check Sum for L21 + L1""" atol = 1e-5 @@ -204,7 +201,7 @@ def test_Sum_l21_l1(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_f1f2f3f4(par: Dict[str, Any]) -> None: +def test_Sum_f1f2f3f4(par: dict[str, Any]) -> None: """Check Sum for f1+f2+f3+f4""" tau = 1.0 @@ -231,7 +228,7 @@ def test_Sum_f1f2f3f4(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_numeric_projection(par: Dict[str, Any]) -> None: +def test_Sum_numeric_projection(par: dict[str, Any]) -> None: """Check Sum for numeric prox + indicator function""" rng = np.random.default_rng(10) diff --git a/pytests/test_grads.py b/pytests/test_grads.py index 6454070..76e3b92 100644 --- a/pytests/test_grads.py +++ b/pytests/test_grads.py @@ -1,7 +1,6 @@ import numpy as np import pytest from pylops.basicoperators import MatrixMult - from pyproximal.proximal import L2 from pyproximal.utils.bilinear import LowRankFactorizedMatrix from pyproximal.utils.gradtest import gradtest_bilinear, gradtest_proximal diff --git a/pytests/test_norms.py b/pytests/test_norms.py index 250adff..2538903 100644 --- a/pytests/test_norms.py +++ b/pytests/test_norms.py @@ -2,7 +2,6 @@ import pytest from numpy.testing import assert_array_almost_equal from pylops.basicoperators import Diagonal, FirstDerivative, Identity, MatrixMult - from pyproximal.proximal import ( L0, L1, diff --git a/pytests/test_projection.py b/pytests/test_projection.py index b2d66e4..e006d79 100644 --- a/pytests/test_projection.py +++ b/pytests/test_projection.py @@ -2,7 +2,6 @@ import pytest from numpy.testing import assert_array_almost_equal from pylops.basicoperators import Identity - from pyproximal.proximal import ( AffineSet, Box, diff --git a/pytests/test_proximal.py b/pytests/test_proximal.py index 679a57b..0ab5308 100644 --- a/pytests/test_proximal.py +++ b/pytests/test_proximal.py @@ -1,9 +1,8 @@ import numpy as np +import pyproximal import pytest from numpy.testing import assert_array_almost_equal, assert_array_equal from pylops import Identity, MatrixMult, Restriction - -import pyproximal from pyproximal.proximal import ( L1, L2, diff --git a/pytests/test_solver.py b/pytests/test_solver.py index 2c532eb..c88dcf7 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -1,20 +1,19 @@ -from typing import Dict, Any +from typing import Any import numpy as np import pytest from numpy.testing import assert_array_almost_equal from pylops.basicoperators import Identity, MatrixMult - from pyproximal.optimization.primal import ( ADMM, ADMML2, HQS, + PPXA, + ConsensusADMM, DouglasRachfordSplitting, GeneralizedProximalGradient, LinearizedADMM, ProximalGradient, - PPXA, - ConsensusADMM, ) from pyproximal.proximal import L1, L2 @@ -231,7 +230,7 @@ def test_ADMM_DRS(par): @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: +def test_PPXA_with_ADMM(par: dict[str, Any]) -> None: """Check equivalency of PPXA and ADMM when using a single regularization term """ @@ -257,7 +256,8 @@ def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: tau = 0.5 / L xadmm, _ = ADMM( - l2, l1, + l2, + l1, x0=np.zeros(m), tau=tau, niter=2000, # niter=1500 makes this test fail for seeds 0 to 499 @@ -274,9 +274,8 @@ def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_PPXA_with_GPG(par: Dict[str, Any]) -> None: - """Check equivalency of PPXA and GeneralizedProximalGradient - """ +def test_PPXA_with_GPG(par: dict[str, Any]) -> None: + """Check equivalency of PPXA and GeneralizedProximalGradient""" np.random.seed(0) n, m = par["n"], par["m"] @@ -318,14 +317,14 @@ def test_PPXA_with_GPG(par: Dict[str, Any]) -> None: [l2_1, l2_2, l1_1, l1_2], x0=np.zeros(m), tau=np.random.uniform(3 * tau, 5 * tau), - show=True + show=True, ) assert_array_almost_equal(xppxa, xgpg, decimal=2) @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: +def test_ConsensusADMM_with_ADMM(par: dict[str, Any]) -> None: """Check equivalency of ConsensusADMM and ADMM when two proximable functions """ @@ -351,11 +350,12 @@ def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: tau = 0.5 / L xadmm, _ = ADMM( - l2, l1, + l2, + l1, x0=np.zeros(m), tau=tau, niter=2000, # niter=1500 makes this test fail for seeds 0 to 499 - show=True + show=True, ) xcadmm = ConsensusADMM( [l2, l1], @@ -368,7 +368,7 @@ def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: +def test_ConsensusADMM_with_ADMM_for_Lasso(par: dict[str, Any]) -> None: """Check equivalency of ConsensusADMM and ADMM when more than two proximable functions for lasso """ @@ -394,7 +394,7 @@ def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: # 1/2||R1||_2^2, 1/2||R2||_2^2, 1/2||R3||_2^2 l2_ops = [ L2(Op=MatrixMult(Ri), b=yi, niter=50, warm=False) - for Ri, yi in zip(R_list, y_list) + for Ri, yi in zip(R_list, y_list, strict=True) ] # 1/2 || [R1; R2; R3] ||_2^2 @@ -432,9 +432,8 @@ def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ConsensusADMM_with_GPG(par: Dict[str, Any]) -> None: - """Check equivalency of ConsensusADMM and GeneralizedProximalGradient - """ +def test_ConsensusADMM_with_GPG(par: dict[str, Any]) -> None: + """Check equivalency of ConsensusADMM and GeneralizedProximalGradient""" np.random.seed(0) diff --git a/tutorials/adaptivepd.py b/tutorials/adaptivepd.py index fa29d79..1585456 100644 --- a/tutorials/adaptivepd.py +++ b/tutorials/adaptivepd.py @@ -16,9 +16,8 @@ import matplotlib.pyplot as plt import numpy as np import pylops -from skimage.data import camera - import pyproximal +from skimage.data import camera plt.close("all") np.random.seed(10) diff --git a/tutorials/basispursuit.py b/tutorials/basispursuit.py index 6147b0b..31025d0 100644 --- a/tutorials/basispursuit.py +++ b/tutorials/basispursuit.py @@ -19,7 +19,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal plt.close("all") diff --git a/tutorials/brainmri.py b/tutorials/brainmri.py index 3866c51..ab9c66d 100644 --- a/tutorials/brainmri.py +++ b/tutorials/brainmri.py @@ -21,9 +21,8 @@ import matplotlib.pyplot as plt import numpy as np import pylops -from scipy.io import loadmat - import pyproximal +from scipy.io import loadmat plt.close("all") np.random.seed(10) diff --git a/tutorials/constrained.py b/tutorials/constrained.py index 3c1af50..7db1486 100644 --- a/tutorials/constrained.py +++ b/tutorials/constrained.py @@ -23,7 +23,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal plt.close("all") diff --git a/tutorials/deblending.py b/tutorials/deblending.py index af8c116..f96092c 100644 --- a/tutorials/deblending.py +++ b/tutorials/deblending.py @@ -24,7 +24,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal np.random.seed(10) diff --git a/tutorials/denoising.py b/tutorials/denoising.py index b845625..af8d194 100644 --- a/tutorials/denoising.py +++ b/tutorials/denoising.py @@ -24,9 +24,8 @@ import matplotlib.pyplot as plt import numpy as np import pylops -from scipy import datasets - import pyproximal +from scipy import datasets plt.close("all") np.random.seed(10) diff --git a/tutorials/groupsparsity.py b/tutorials/groupsparsity.py index c91a138..612066f 100644 --- a/tutorials/groupsparsity.py +++ b/tutorials/groupsparsity.py @@ -27,7 +27,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal plt.close("all") @@ -76,8 +75,9 @@ np.random.seed(10) perc_subsampling = (0.1, 0.6) -Nsub1, Nsub2 = int(np.round(N * perc_subsampling[0])), int( - np.round(N * perc_subsampling[1]) +Nsub1, Nsub2 = ( + int(np.round(N * perc_subsampling[0])), + int(np.round(N * perc_subsampling[1])), ) iava1 = np.sort(np.random.permutation(np.arange(N))[:Nsub1]) iava2 = np.sort(np.random.permutation(np.arange(N))[:Nsub2]) diff --git a/tutorials/hankel_matrix_estimation.py b/tutorials/hankel_matrix_estimation.py index 452ee01..929ca48 100644 --- a/tutorials/hankel_matrix_estimation.py +++ b/tutorials/hankel_matrix_estimation.py @@ -27,11 +27,10 @@ import matplotlib.pyplot as plt import numpy as np -from scipy.linalg import hankel - from pyproximal.optimization.primal import ADMM from pyproximal.projection import HankelProj from pyproximal.proximal import Hankel, QuadraticEnvelopeRankL2 +from scipy.linalg import hankel plt.close("all") np.random.seed(0) @@ -48,7 +47,7 @@ t = np.linspace(-1, 1, 200) f_gt = np.zeros_like(t) -for i in range(n_signal): +for _ in range(n_signal): d = np.random.uniform(-1, 1) phi = np.random.uniform(-40 * np.pi, 40 * np.pi) t0 = np.random.uniform(-1, 1) diff --git a/tutorials/lowrankcompletion.py b/tutorials/lowrankcompletion.py index a49e914..4f09164 100644 --- a/tutorials/lowrankcompletion.py +++ b/tutorials/lowrankcompletion.py @@ -40,9 +40,8 @@ # sphinx_gallery_thumbnail_number = 2 import numpy as np import pylops -from scipy import datasets - import pyproximal +from scipy import datasets plt.close("all") np.random.seed(0) diff --git a/tutorials/matrixfactorization.py b/tutorials/matrixfactorization.py index c9023a4..7e918e4 100644 --- a/tutorials/matrixfactorization.py +++ b/tutorials/matrixfactorization.py @@ -20,7 +20,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal plt.close("all") diff --git a/tutorials/nonlinearconstrained.py b/tutorials/nonlinearconstrained.py index afee690..4396992 100644 --- a/tutorials/nonlinearconstrained.py +++ b/tutorials/nonlinearconstrained.py @@ -46,9 +46,8 @@ import matplotlib.pyplot as plt import numpy as np -import scipy as sp - import pyproximal +import scipy as sp plt.close("all") np.random.seed(10) @@ -101,7 +100,7 @@ def grad(self, x): def optimize(self): self.solhist = [] sol = self.x0.copy() - for iiter in range(self.niter): + for _ in range(self.niter): x1, x2 = sol dfx1, dfx2 = self._gradprox(sol, self.tau) x1 -= self.alpha * dfx1 @@ -123,7 +122,7 @@ def optimize(self): steps = [ (0, 0), ] -for iiter in range(niters): +for _ in range(niters): x, y = steps[-1] dfx, dfy = rosenbrock_grad(x, y) x -= alpha * dfx diff --git a/tutorials/nrsfm.py b/tutorials/nrsfm.py index f5e53ce..4d3260d 100644 --- a/tutorials/nrsfm.py +++ b/tutorials/nrsfm.py @@ -62,7 +62,7 @@ def plot_first_3d_pose(ax, X, color="b", marker="o", linecolor="k"): ax.scatter(X[0, :], X[1, :], X[2, :], color, marker=marker) - for j, ind in enumerate(markers.values()): + for _, ind in enumerate(markers.values()): ax.plot(X[0, ind], X[1, ind], X[2, ind], "-", color=linecolor) ax.set_box_aspect(np.ptp(X[:3, :], axis=1)) ax.view_init(20, 25) @@ -289,8 +289,8 @@ def prox(self, x, tau): # Furthermore, we compute some statistics on the reconstruction performance. You # can vary the regulation strength :math:`\mu` to see if you can achieve better # performance yourself! -print(f'Datafit: {np.linalg.norm(Rblk @ X_rec - M, "fro")}') -print(f'Distance to GT: {np.linalg.norm(X_rec - X_gt, "fro")}') +print(f"Datafit: {np.linalg.norm(Rblk @ X_rec - M, 'fro')}") +print(f"Distance to GT: {np.linalg.norm(X_rec - X_gt, 'fro')}") ############################################################################### # One issue with the nuclear norm is that you have the hyperparameter diff --git a/tutorials/plugandplay.py b/tutorials/plugandplay.py index 02db781..eaf704b 100644 --- a/tutorials/plugandplay.py +++ b/tutorials/plugandplay.py @@ -26,9 +26,8 @@ import matplotlib.pyplot as plt import numpy as np import pylops -from pylops.config import set_ndarray_multiplication - import pyproximal +from pylops.config import set_ndarray_multiplication plt.close("all") np.random.seed(0) diff --git a/tutorials/relaxed_mumford-shah.py b/tutorials/relaxed_mumford-shah.py index 150cb0f..a626c21 100644 --- a/tutorials/relaxed_mumford-shah.py +++ b/tutorials/relaxed_mumford-shah.py @@ -31,7 +31,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal np.random.seed(1) diff --git a/tutorials/segmentation.py b/tutorials/segmentation.py index e8adc6a..c2230a8 100644 --- a/tutorials/segmentation.py +++ b/tutorials/segmentation.py @@ -20,7 +20,6 @@ import matplotlib.pyplot as plt import numpy as np - import pyproximal plt.close("all") diff --git a/tutorials/twist.py b/tutorials/twist.py index 104a5d6..a255ef0 100644 --- a/tutorials/twist.py +++ b/tutorials/twist.py @@ -29,7 +29,6 @@ import matplotlib.pyplot as plt import numpy as np import pylops - import pyproximal plt.close("all") From 1b6b5d738ddfab3fdd2bc841790806ed7a122550 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 17 Feb 2026 21:59:43 +0000 Subject: [PATCH 49/72] minor: small improvements to Makefile --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2a476a8..b319e76 100755 --- a/Makefile +++ b/Makefile @@ -97,11 +97,12 @@ typeannot_uv: $(UV) run mypy pyproximal/ coverage: - coverage run -m pytest && coverage xml && coverage html && $(PYTHON) -m http.server --directory htmlcov/ + coverage run --source=pyproximal -m pytest && \ + coverage xml && coverage html && $(PYTHON) -m http.server --directory htmlcov/ coverage_uv: make uvcheck - $(UV) run coverage run -m pytest &&\ + $(UV) run coverage run --source=pyproximal -m pytest &&\ $(UV) run coverage xml &&\ $(UV) run coverage html &&\ $(UV) run python -m http.server --directory htmlcov/ From e160f5e95ee5f47f7f60efb5fbf7d0339a609307 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 17 Feb 2026 22:00:12 +0000 Subject: [PATCH 50/72] minor: change target branch in codacy GA --- .github/workflows/codacy-coverage-reporter.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index eb5c7b3..579c725 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -5,7 +5,7 @@ name: PyProximal-coverage on: pull_request: push: - branches: [main] + branches: [dev] jobs: build: From 84dd83b2b7c8f4db5af44838dee5ab10d405e534 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Tue, 17 Feb 2026 22:25:46 +0000 Subject: [PATCH 51/72] minor: minimize ruff ignores --- docs/source/conf.py | 3 ++- examples/plot_concave_penalties.py | 1 + examples/plot_dykstra.py | 1 + examples/plot_indicators.py | 1 + examples/plot_norms.py | 1 + examples/plot_quadratic.py | 1 + pyproject.toml | 8 +++++--- pyproximal/ProxOperator.py | 2 +- pyproximal/optimization/bregman.py | 2 +- pyproximal/optimization/pnp.py | 2 +- pyproximal/optimization/primal.py | 2 +- pyproximal/optimization/primaldual.py | 2 +- pyproximal/optimization/segmentation.py | 2 +- pyproximal/proximal/GenericIntersection.py | 2 +- pyproximal/proximal/L0.py | 2 +- pyproximal/proximal/L1.py | 2 +- pyproximal/proximal/L2.py | 2 +- pyproximal/proximal/RelaxedMS.py | 2 +- pyproximal/proximal/Sum.py | 2 +- pyproximal/proximal/TV.py | 2 +- pyproximal/proximal/_Dykstra.py | 2 +- pyproximal/proximal/_Simplex_numba.py | 3 +-- pytests/test_basic.py | 1 + pytests/test_bilinear.py | 1 + pytests/test_concave_penalties.py | 1 + pytests/test_dykstra.py | 1 + pytests/test_grads.py | 1 + pytests/test_norms.py | 1 + pytests/test_projection.py | 1 + pytests/test_proximal.py | 3 ++- pytests/test_solver.py | 1 + tutorials/adaptivepd.py | 3 ++- tutorials/basispursuit.py | 1 + tutorials/brainmri.py | 3 ++- tutorials/constrained.py | 1 + tutorials/deblending.py | 1 + tutorials/denoising.py | 3 ++- tutorials/groupsparsity.py | 1 + tutorials/hankel_matrix_estimation.py | 3 ++- tutorials/lowrankcompletion.py | 3 ++- tutorials/matrixfactorization.py | 1 + tutorials/nonlinearconstrained.py | 3 ++- tutorials/nrsfm.py | 13 ++++++++----- tutorials/plugandplay.py | 3 ++- tutorials/relaxed_mumford-shah.py | 1 + tutorials/segmentation.py | 1 + tutorials/twist.py | 1 + 47 files changed, 67 insertions(+), 33 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 1b468c9..d6e3ff1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -2,9 +2,10 @@ import os import sys -from pyproximal import __version__ from sphinx_gallery.sorting import ExampleTitleSortKey +from pyproximal import __version__ + # Sphinx needs to be able to import the package to use autodoc and # get the version number sys.path.insert(0, os.path.abspath("../../pyproximal")) diff --git a/examples/plot_concave_penalties.py b/examples/plot_concave_penalties.py index 65cf921..cbbaa0c 100644 --- a/examples/plot_concave_penalties.py +++ b/examples/plot_concave_penalties.py @@ -7,6 +7,7 @@ import matplotlib.pyplot as plt import numpy as np + import pyproximal plt.close("all") diff --git a/examples/plot_dykstra.py b/examples/plot_dykstra.py index 485b738..6aa0605 100644 --- a/examples/plot_dykstra.py +++ b/examples/plot_dykstra.py @@ -15,6 +15,7 @@ from matplotlib.colors import to_rgba from matplotlib.patches import Circle, Rectangle from pylops import MatrixMult + from pyproximal.projection import BoxProj, EuclideanBallProj, GenericIntersectionProj from pyproximal.proximal import L1, L2, Box, EuclideanBall, GenericIntersectionProx, Sum diff --git a/examples/plot_indicators.py b/examples/plot_indicators.py index f03e4f3..9d70a8c 100755 --- a/examples/plot_indicators.py +++ b/examples/plot_indicators.py @@ -8,6 +8,7 @@ import matplotlib.pyplot as plt import numpy as np + import pyproximal plt.close("all") diff --git a/examples/plot_norms.py b/examples/plot_norms.py index 1f486c2..bd80d37 100755 --- a/examples/plot_norms.py +++ b/examples/plot_norms.py @@ -14,6 +14,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal plt.close("all") diff --git a/examples/plot_quadratic.py b/examples/plot_quadratic.py index 8f93727..a8a9795 100755 --- a/examples/plot_quadratic.py +++ b/examples/plot_quadratic.py @@ -15,6 +15,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal plt.close("all") diff --git a/pyproject.toml b/pyproject.toml index e57c81f..fc6022b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,8 +97,7 @@ select = [ "I" # isort ] ignore = [ - "E501", # line too long (handled by formatter like Black) - "I001", # allow usorted imports + "E501", # line too long (for comments and strings) "UP031", # use of old '%' formatting instead of f-strings ] @@ -107,12 +106,15 @@ ignore = [ "F401", # imported but unused (re-exports) "F403", # star import used for public API "F405", # name may be undefined from star import + "I001", # allow usorted imports ] "tutorials/*.py" = [ - "E402", # module level import not at top of file "E731", # do not assign a lambda expression, use def ] +[tool.ruff.lint.isort] +known-first-party = ["pyproximal"] + [tool.mypy] files = ["pyproximal", ] python_version = "3.11" diff --git a/pyproximal/ProxOperator.py b/pyproximal/ProxOperator.py index a3ff3ff..f32d07c 100644 --- a/pyproximal/ProxOperator.py +++ b/pyproximal/ProxOperator.py @@ -1,5 +1,5 @@ -from typing import TYPE_CHECKING, Any, Optional, Union from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Optional, Union import numpy as np from pylops.utils.typing import NDArray diff --git a/pyproximal/optimization/bregman.py b/pyproximal/optimization/bregman.py index 54fa2e7..851eeb4 100644 --- a/pyproximal/optimization/bregman.py +++ b/pyproximal/optimization/bregman.py @@ -1,7 +1,7 @@ import time +from collections.abc import Callable from copy import deepcopy from typing import TYPE_CHECKING, Any, Optional -from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray diff --git a/pyproximal/optimization/pnp.py b/pyproximal/optimization/pnp.py index bba884d..d40bf26 100644 --- a/pyproximal/optimization/pnp.py +++ b/pyproximal/optimization/pnp.py @@ -1,5 +1,5 @@ -from typing import Any from collections.abc import Callable +from typing import Any from pylops.utils.typing import NDArray, ShapeLike diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index b85d78b..f04d988 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -1,8 +1,8 @@ import time import warnings +from collections.abc import Callable from math import sqrt from typing import TYPE_CHECKING, Any, Optional -from collections.abc import Callable import numpy as np from pylops.optimization.leastsquares import regularized_inversion diff --git a/pyproximal/optimization/primaldual.py b/pyproximal/optimization/primaldual.py index 5b8289d..28c1637 100644 --- a/pyproximal/optimization/primaldual.py +++ b/pyproximal/optimization/primaldual.py @@ -1,6 +1,6 @@ import time -from typing import TYPE_CHECKING from collections.abc import Callable +from typing import TYPE_CHECKING import numpy as np from pylops.utils.backend import get_array_module, to_numpy diff --git a/pyproximal/optimization/segmentation.py b/pyproximal/optimization/segmentation.py index 0972867..51bf084 100644 --- a/pyproximal/optimization/segmentation.py +++ b/pyproximal/optimization/segmentation.py @@ -1,5 +1,5 @@ -from typing import Any from collections.abc import Callable +from typing import Any import numpy as np from pylops import BlockDiag, Gradient diff --git a/pyproximal/proximal/GenericIntersection.py b/pyproximal/proximal/GenericIntersection.py index ef59520..dff8884 100644 --- a/pyproximal/proximal/GenericIntersection.py +++ b/pyproximal/proximal/GenericIntersection.py @@ -1,5 +1,5 @@ -from typing import Any from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray diff --git a/pyproximal/proximal/L0.py b/pyproximal/proximal/L0.py index 710cf04..00612a5 100644 --- a/pyproximal/proximal/L0.py +++ b/pyproximal/proximal/L0.py @@ -1,6 +1,6 @@ import warnings -from typing import Any from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray diff --git a/pyproximal/proximal/L1.py b/pyproximal/proximal/L1.py index b2e3530..5af3bfa 100644 --- a/pyproximal/proximal/L1.py +++ b/pyproximal/proximal/L1.py @@ -1,5 +1,5 @@ -from typing import Any from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray diff --git a/pyproximal/proximal/L2.py b/pyproximal/proximal/L2.py index 8d1b1a7..2fb271b 100644 --- a/pyproximal/proximal/L2.py +++ b/pyproximal/proximal/L2.py @@ -1,5 +1,5 @@ -from typing import TYPE_CHECKING, Any, Optional from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Optional import numpy as np from pylops import Identity, MatrixMult diff --git a/pyproximal/proximal/RelaxedMS.py b/pyproximal/proximal/RelaxedMS.py index 1d7eea9..92ac1de 100644 --- a/pyproximal/proximal/RelaxedMS.py +++ b/pyproximal/proximal/RelaxedMS.py @@ -1,5 +1,5 @@ -from typing import Any from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray diff --git a/pyproximal/proximal/Sum.py b/pyproximal/proximal/Sum.py index f5a358c..e20e13d 100644 --- a/pyproximal/proximal/Sum.py +++ b/pyproximal/proximal/Sum.py @@ -1,5 +1,5 @@ -from typing import Any from collections.abc import Callable +from typing import Any from pylops.utils.backend import get_array_module from pylops.utils.typing import NDArray diff --git a/pyproximal/proximal/TV.py b/pyproximal/proximal/TV.py index e9e161e..bf32148 100644 --- a/pyproximal/proximal/TV.py +++ b/pyproximal/proximal/TV.py @@ -1,6 +1,6 @@ +from collections.abc import Callable from copy import deepcopy from typing import Any -from collections.abc import Callable import numpy as np from pylops import FirstDerivative, Gradient diff --git a/pyproximal/proximal/_Dykstra.py b/pyproximal/proximal/_Dykstra.py index 07447d9..3596435 100644 --- a/pyproximal/proximal/_Dykstra.py +++ b/pyproximal/proximal/_Dykstra.py @@ -1,5 +1,5 @@ -from typing import Any, TypeVar from collections.abc import Callable, Sequence +from typing import Any, TypeVar from pylops.utils.backend import get_array_module from pylops.utils.typing import NDArray diff --git a/pyproximal/proximal/_Simplex_numba.py b/pyproximal/proximal/_Simplex_numba.py index f2feb6a..f04537b 100644 --- a/pyproximal/proximal/_Simplex_numba.py +++ b/pyproximal/proximal/_Simplex_numba.py @@ -1,9 +1,8 @@ -from typing import Any import os +from typing import Any import numpy as np from numba import jit - from pylops.utils.typing import NDArray # detect whether to use parallel or not diff --git a/pytests/test_basic.py b/pytests/test_basic.py index 81d130a..d826e30 100644 --- a/pytests/test_basic.py +++ b/pytests/test_basic.py @@ -2,6 +2,7 @@ import pytest from numpy.testing import assert_array_equal from pylops import MatrixMult + from pyproximal.proximal import L1, L2, Euclidean, Quadratic par1 = {"ny": 21, "nx": 11, "nt": 20, "imag": 0, "dtype": "float32"} # real diff --git a/pytests/test_bilinear.py b/pytests/test_bilinear.py index 4257cf2..30c0908 100644 --- a/pytests/test_bilinear.py +++ b/pytests/test_bilinear.py @@ -2,6 +2,7 @@ import pytest from numpy.testing import assert_array_almost_equal from pylops import Diagonal + from pyproximal.utils.bilinear import LowRankFactorizedMatrix par1 = {"n": 21, "m": 11, "k": 5, "imag": 0, "dtype": "float32"} # real diff --git a/pytests/test_concave_penalties.py b/pytests/test_concave_penalties.py index 864ce4c..a61dea9 100644 --- a/pytests/test_concave_penalties.py +++ b/pytests/test_concave_penalties.py @@ -1,5 +1,6 @@ import numpy as np import pytest + from pyproximal.proximal import ETP, SCAD, Geman, Log, Log1, QuadraticEnvelopeCard from pyproximal.utils import moreau diff --git a/pytests/test_dykstra.py b/pytests/test_dykstra.py index d1d8d97..efe549a 100644 --- a/pytests/test_dykstra.py +++ b/pytests/test_dykstra.py @@ -3,6 +3,7 @@ import numpy as np import pytest + from pyproximal.projection import ( BoxProj, EuclideanBallProj, diff --git a/pytests/test_grads.py b/pytests/test_grads.py index 76e3b92..6454070 100644 --- a/pytests/test_grads.py +++ b/pytests/test_grads.py @@ -1,6 +1,7 @@ import numpy as np import pytest from pylops.basicoperators import MatrixMult + from pyproximal.proximal import L2 from pyproximal.utils.bilinear import LowRankFactorizedMatrix from pyproximal.utils.gradtest import gradtest_bilinear, gradtest_proximal diff --git a/pytests/test_norms.py b/pytests/test_norms.py index 2538903..250adff 100644 --- a/pytests/test_norms.py +++ b/pytests/test_norms.py @@ -2,6 +2,7 @@ import pytest from numpy.testing import assert_array_almost_equal from pylops.basicoperators import Diagonal, FirstDerivative, Identity, MatrixMult + from pyproximal.proximal import ( L0, L1, diff --git a/pytests/test_projection.py b/pytests/test_projection.py index e006d79..b2d66e4 100644 --- a/pytests/test_projection.py +++ b/pytests/test_projection.py @@ -2,6 +2,7 @@ import pytest from numpy.testing import assert_array_almost_equal from pylops.basicoperators import Identity + from pyproximal.proximal import ( AffineSet, Box, diff --git a/pytests/test_proximal.py b/pytests/test_proximal.py index 0ab5308..679a57b 100644 --- a/pytests/test_proximal.py +++ b/pytests/test_proximal.py @@ -1,8 +1,9 @@ import numpy as np -import pyproximal import pytest from numpy.testing import assert_array_almost_equal, assert_array_equal from pylops import Identity, MatrixMult, Restriction + +import pyproximal from pyproximal.proximal import ( L1, L2, diff --git a/pytests/test_solver.py b/pytests/test_solver.py index c88dcf7..b0649ef 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -4,6 +4,7 @@ import pytest from numpy.testing import assert_array_almost_equal from pylops.basicoperators import Identity, MatrixMult + from pyproximal.optimization.primal import ( ADMM, ADMML2, diff --git a/tutorials/adaptivepd.py b/tutorials/adaptivepd.py index 1585456..fa29d79 100644 --- a/tutorials/adaptivepd.py +++ b/tutorials/adaptivepd.py @@ -16,9 +16,10 @@ import matplotlib.pyplot as plt import numpy as np import pylops -import pyproximal from skimage.data import camera +import pyproximal + plt.close("all") np.random.seed(10) diff --git a/tutorials/basispursuit.py b/tutorials/basispursuit.py index 31025d0..6147b0b 100644 --- a/tutorials/basispursuit.py +++ b/tutorials/basispursuit.py @@ -19,6 +19,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal plt.close("all") diff --git a/tutorials/brainmri.py b/tutorials/brainmri.py index ab9c66d..3866c51 100644 --- a/tutorials/brainmri.py +++ b/tutorials/brainmri.py @@ -21,9 +21,10 @@ import matplotlib.pyplot as plt import numpy as np import pylops -import pyproximal from scipy.io import loadmat +import pyproximal + plt.close("all") np.random.seed(10) diff --git a/tutorials/constrained.py b/tutorials/constrained.py index 7db1486..3c1af50 100644 --- a/tutorials/constrained.py +++ b/tutorials/constrained.py @@ -23,6 +23,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal plt.close("all") diff --git a/tutorials/deblending.py b/tutorials/deblending.py index f96092c..af8c116 100644 --- a/tutorials/deblending.py +++ b/tutorials/deblending.py @@ -24,6 +24,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal np.random.seed(10) diff --git a/tutorials/denoising.py b/tutorials/denoising.py index af8d194..b845625 100644 --- a/tutorials/denoising.py +++ b/tutorials/denoising.py @@ -24,9 +24,10 @@ import matplotlib.pyplot as plt import numpy as np import pylops -import pyproximal from scipy import datasets +import pyproximal + plt.close("all") np.random.seed(10) diff --git a/tutorials/groupsparsity.py b/tutorials/groupsparsity.py index 612066f..944ae92 100644 --- a/tutorials/groupsparsity.py +++ b/tutorials/groupsparsity.py @@ -27,6 +27,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal plt.close("all") diff --git a/tutorials/hankel_matrix_estimation.py b/tutorials/hankel_matrix_estimation.py index 929ca48..d2f02c6 100644 --- a/tutorials/hankel_matrix_estimation.py +++ b/tutorials/hankel_matrix_estimation.py @@ -27,10 +27,11 @@ import matplotlib.pyplot as plt import numpy as np +from scipy.linalg import hankel + from pyproximal.optimization.primal import ADMM from pyproximal.projection import HankelProj from pyproximal.proximal import Hankel, QuadraticEnvelopeRankL2 -from scipy.linalg import hankel plt.close("all") np.random.seed(0) diff --git a/tutorials/lowrankcompletion.py b/tutorials/lowrankcompletion.py index 4f09164..a49e914 100644 --- a/tutorials/lowrankcompletion.py +++ b/tutorials/lowrankcompletion.py @@ -40,9 +40,10 @@ # sphinx_gallery_thumbnail_number = 2 import numpy as np import pylops -import pyproximal from scipy import datasets +import pyproximal + plt.close("all") np.random.seed(0) diff --git a/tutorials/matrixfactorization.py b/tutorials/matrixfactorization.py index 7e918e4..c9023a4 100644 --- a/tutorials/matrixfactorization.py +++ b/tutorials/matrixfactorization.py @@ -20,6 +20,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal plt.close("all") diff --git a/tutorials/nonlinearconstrained.py b/tutorials/nonlinearconstrained.py index 4396992..c2689bc 100644 --- a/tutorials/nonlinearconstrained.py +++ b/tutorials/nonlinearconstrained.py @@ -46,9 +46,10 @@ import matplotlib.pyplot as plt import numpy as np -import pyproximal import scipy as sp +import pyproximal + plt.close("all") np.random.seed(10) diff --git a/tutorials/nrsfm.py b/tutorials/nrsfm.py index 4d3260d..5b70684 100644 --- a/tutorials/nrsfm.py +++ b/tutorials/nrsfm.py @@ -48,8 +48,16 @@ import numpy as np import scipy as sp +from pyproximal import Nuclear, ProxOperator +from pyproximal.optimization.primal import ADMM +from pyproximal.ProxOperator import _check_tau + plt.close("all") np.random.seed(0) + +############################################################################### +# Let's start by loading the data. + data = np.load("../testdata/mocap.npz", allow_pickle=True) X_gt = data["X_gt"] markers = data["markers"].item() @@ -209,9 +217,6 @@ def unstack(Xs: np.ndarray): # are simply those of the (stacked) nuclear norm and the Frobenius norm. # We implement these next: -from pyproximal import Nuclear, ProxOperator -from pyproximal.ProxOperator import _check_tau - class BlockDiagFrobenius(ProxOperator): r"""Proximal operator for 1/2 * ||RX - M||_F^2 where R is block-diagonal. @@ -262,8 +267,6 @@ def prox(self, x, tau): ############################################################################### # Now we are ready to solve the problem using ADMM. -from pyproximal.optimization.primal import ADMM - mu = 1 R = data["R"] Rblk = sp.linalg.block_diag(*R) diff --git a/tutorials/plugandplay.py b/tutorials/plugandplay.py index eaf704b..02db781 100644 --- a/tutorials/plugandplay.py +++ b/tutorials/plugandplay.py @@ -26,9 +26,10 @@ import matplotlib.pyplot as plt import numpy as np import pylops -import pyproximal from pylops.config import set_ndarray_multiplication +import pyproximal + plt.close("all") np.random.seed(0) set_ndarray_multiplication(False) diff --git a/tutorials/relaxed_mumford-shah.py b/tutorials/relaxed_mumford-shah.py index a626c21..150cb0f 100644 --- a/tutorials/relaxed_mumford-shah.py +++ b/tutorials/relaxed_mumford-shah.py @@ -31,6 +31,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal np.random.seed(1) diff --git a/tutorials/segmentation.py b/tutorials/segmentation.py index c2230a8..e8adc6a 100644 --- a/tutorials/segmentation.py +++ b/tutorials/segmentation.py @@ -20,6 +20,7 @@ import matplotlib.pyplot as plt import numpy as np + import pyproximal plt.close("all") diff --git a/tutorials/twist.py b/tutorials/twist.py index a255ef0..104a5d6 100644 --- a/tutorials/twist.py +++ b/tutorials/twist.py @@ -29,6 +29,7 @@ import matplotlib.pyplot as plt import numpy as np import pylops + import pyproximal plt.close("all") From a649e3f49082278caad6d1cd836be0210590012d Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 21 Feb 2026 21:48:20 +0000 Subject: [PATCH 52/72] test: increase test coverage --- pyproximal/proximal/Quadratic.py | 6 +-- pytests/test_concave_penalties.py | 6 +++ pytests/test_dykstra.py | 7 +++ pytests/test_norms.py | 58 ++++++++++++++++++++++++- pytests/test_projection.py | 38 ++++++++++++++++ pytests/test_proximal.py | 72 +++++++++++++++++++++++++++---- 6 files changed, 173 insertions(+), 14 deletions(-) diff --git a/pyproximal/proximal/Quadratic.py b/pyproximal/proximal/Quadratic.py index 8a6ebfd..b4ef387 100644 --- a/pyproximal/proximal/Quadratic.py +++ b/pyproximal/proximal/Quadratic.py @@ -108,7 +108,7 @@ def prox(self, x: NDArray, tau: float) -> NDArray: Op1 = MatrixMult(np.eye(self.Op.shape[0]) + tau * self.Op.A) x = Op1.div(y) else: - Op1 = Identity(self.Op.shape[0], dtype=self.Op.dtype) + tau * self.Op.A + Op1 = Identity(self.Op.shape[0], dtype=self.Op.dtype) + tau * self.Op x = lsqr(Op1, y, iter_lim=self.niter, x0=self.x0)[0] if self.warm: self.x0 = x @@ -131,9 +131,9 @@ def grad(self, x: NDArray) -> NDArray: """ if self.Op is not None and self.b is not None: - g = self.Op.matvec(x) / 2.0 + x + g = self.Op.matvec(x) + self.b elif self.b is not None: - g = x + g = self.b else: g = 0.0 return g diff --git a/pytests/test_concave_penalties.py b/pytests/test_concave_penalties.py index a61dea9..1db568a 100644 --- a/pytests/test_concave_penalties.py +++ b/pytests/test_concave_penalties.py @@ -101,7 +101,9 @@ def test_ETP(par): def test_Geman(par): """Geman penalty and proximal/dual proximal""" np.random.seed(10) + geman = Geman(sigma=par["sigma"], gamma=par["gamma"]) + # Geman x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) expected = par["sigma"] * np.linalg.norm(np.abs(x) / (np.abs(x) + par["gamma"]), 1) @@ -116,7 +118,9 @@ def test_Geman(par): def test_QuadraticEnvelopeCard(par): """QuadraticEnvelopeCard penalty and proximal/dual proximal""" np.random.seed(10) + fmu = QuadraticEnvelopeCard(mu=par["mu"]) + # Quadratic envelope of the l0-penalty x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) expected = np.linalg.norm( @@ -127,3 +131,5 @@ def test_QuadraticEnvelopeCard(par): # Check proximal operator tau = 0.25 assert moreau(fmu, x, tau) + tau = 1.25 + assert moreau(fmu, x, tau) diff --git a/pytests/test_dykstra.py b/pytests/test_dykstra.py index efe549a..5dfdedb 100644 --- a/pytests/test_dykstra.py +++ b/pytests/test_dykstra.py @@ -28,6 +28,13 @@ par2prox = {"nx": 11, "ny": 14, "sigma": 2.0, "dtype": "float64"} # odd float64 +def test_GenericIntersectionProx_empty(): + """Test GenericIntersectionProx raises an error when the list of + projections is empty""" + with pytest.raises(ValueError, match="items must not be empty"): + GenericIntersectionProx([]) + + @pytest.mark.parametrize("par", [(par1proj), (par2proj)]) def test_GenericIntersectionProx(par: dict[str, Any]) -> None: """GenericIntersectionProx and proximal/dual proximal of related indicator""" diff --git a/pytests/test_norms.py b/pytests/test_norms.py index 250adff..a0ff4e5 100644 --- a/pytests/test_norms.py +++ b/pytests/test_norms.py @@ -27,6 +27,8 @@ @pytest.mark.parametrize("par", [(par1), (par2)]) def test_Euclidean(par): """Euclidean norm and proximal/dual proximal""" + np.random.seed(10) + eucl = Euclidean(sigma=par["sigma"]) # norm @@ -46,6 +48,8 @@ def test_L2_op(par): """L2 norm of Op*x and proximal (since Op is a Diagonal operator the denominator becomes 1 + sigma*tau*d[i]^2 for every i)""" + np.random.seed(10) + b = np.zeros(par["nx"], dtype=par["dtype"]) d = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) l2 = L2(Op=Diagonal(d, dtype=par["dtype"]), b=b, sigma=par["sigma"], niter=500) @@ -65,6 +69,8 @@ def test_L2_op(par): def test_L2_op_solver(par): """L2 norm of Op*x-b and proximal, the first compared to close-form solution and the second with different choices of solver.""" + np.random.seed(10) + Op = MatrixMult( np.random.normal(0, 1, (par["nx"], par["nx"])).astype(dtype=par["dtype"]), dtype=par["dtype"], @@ -94,6 +100,8 @@ def test_L2_dense(par): compared to closed-form solution (since Op is a Diagonal operator the denominator becomes 1 + sigma*tau*d[i]^2 for every i)""" + np.random.seed(10) + for densesolver in ("numpy", "scipy", "factorize"): b = np.zeros(par["nx"], dtype=par["dtype"]) d = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) @@ -118,6 +126,8 @@ def test_L2_dense(par): def test_L2_diff(par): """L2 norm of difference (x-b) and proximal compared to closed-form solution""" + np.random.seed(10) + b = np.ones(par["nx"], dtype=par["dtype"]) l2 = L2(b=b, sigma=par["sigma"]) @@ -137,6 +147,8 @@ def test_L2_x(par): """L2 norm of x and proximal (implemented both directly and with identity operator and zero b and compared to closed-form solution)""" + np.random.seed(10) + l2 = L2( Op=Identity(par["nx"], dtype=par["dtype"]), b=np.zeros(par["nx"], dtype=par["dtype"]), @@ -160,6 +172,8 @@ def test_L2_x(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L1(par): """L1 norm and proximal/dual proximal""" + np.random.seed(10) + l1 = L1(sigma=par["sigma"]) # norm @@ -171,9 +185,27 @@ def test_L1(par): assert moreau(l1, x, tau) +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_L1_func(par): + """L1 norm and proximal/dual proximal with sigma as callable""" + np.random.seed(10) + + l1 = L1(sigma=lambda x: par["sigma"]) + + # norm + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + assert l1(x) == par["sigma"] * np.sum(np.abs(x)) + + # prox / dualprox + tau = 2.0 + assert moreau(l1, x, tau) + + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L1_diff(par): """L1 norm of difference and proximal/dual proximal""" + np.random.seed(10) + g = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) l1 = L1(sigma=par["sigma"], g=g) @@ -189,6 +221,8 @@ def test_L1_diff(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L0(par): """L0 norm and proximal/dual proximal""" + np.random.seed(10) + l0 = L0(sigma=par["sigma"]) # norm @@ -205,6 +239,8 @@ def test_L21(par): """L21 norm and proximal/dual proximal on 2d array (compare it with N L2 norms on the columns of the 2d array """ + np.random.seed(10) + l21 = L21(ndim=2, sigma=par["sigma"]) # norm @@ -220,6 +256,8 @@ def test_L21(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L21_plus_L1(par): """L21 plus L1 norm on 2darray.""" + np.random.seed(10) + l21_plus_l1 = L21_plus_L1(sigma=par["sigma"], rho=0.8) # norm @@ -239,6 +277,8 @@ def test_L21_plus_L1(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_Huber(par): """Huber norm and proximal/dual proximal""" + np.random.seed(10) + hub = Huber(alpha=par["sigma"]) # norm @@ -253,14 +293,21 @@ def test_Huber(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_HuberCircular(par): """Circular Huber norm and proximal/dual proximal""" + np.random.seed(10) + hub = HuberCircular(alpha=par["sigma"]) # norm x = np.random.uniform(0.0, 0.1, par["nx"]).astype(par["dtype"]) - x = ( + x_below = ( (0.1 * par["sigma"]) * x / np.linalg.norm(x) ) # to ensure that is smaller than sigma - assert hub(x) == np.linalg.norm(x) ** 2 / (2 * par["sigma"]) + assert hub(x_below) == np.linalg.norm(x_below) ** 2 / (2 * par["sigma"]) + + x_above = ( + (2.0 * par["sigma"]) * x / np.linalg.norm(x) + ) # to ensure that is larger than sigma + assert hub(x_above) == np.linalg.norm(x_above) - 0.5 * par["sigma"] # prox / dualprox tau = 2.0 @@ -270,7 +317,10 @@ def test_HuberCircular(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_TV(par): """TV norm of x and proximal""" + np.random.seed(10) + tv = TV(dims=(par["nx"],), sigma=par["sigma"]) + # norm x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) derivOp = FirstDerivative(par["nx"], dtype=par["dtype"], kind="forward") @@ -304,6 +354,8 @@ def test_Nuclear_FOM(): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_Nuclear(par): """Nuclear norm and proximal/dual proximal""" + np.random.seed(10) + nucl = Nuclear((par["nx"], 2 * par["nx"]), sigma=par["sigma"]) # norm, cross-check with svd (use tolerance as two methods don't provide @@ -320,6 +372,8 @@ def test_Nuclear(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_Weighted_Nuclear(par): """Weighted nuclear norm and proximal/dual proximal""" + np.random.seed(10) + weights = par["sigma"] * np.linspace(0.1, 5, 2 * par["nx"]) nucl = Nuclear((par["nx"], 2 * par["nx"]), sigma=weights) diff --git a/pytests/test_projection.py b/pytests/test_projection.py index b2d66e4..9e22279 100644 --- a/pytests/test_projection.py +++ b/pytests/test_projection.py @@ -82,6 +82,25 @@ def test_L0Ball(par): assert moreau(l0, x, tau) +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_L0Ball_func(par): + """L0 Ball projection and proximal/dual proximal of related indicator + with sigma as callable""" + np.random.seed(10) + + l0 = L0Ball(lambda x: 1) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + 1.0 + + # evaluation + assert l0(x) is False + xp = l0.prox(x, 1.0) + assert l0(xp) is True + + # prox / dualprox + tau = 2.0 + assert moreau(l0, x, tau) + + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L10Ball(par): """L10 Ball projection and proximal/dual proximal of related indicator""" @@ -100,6 +119,25 @@ def test_L10Ball(par): assert moreau(l0, x, tau) +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_L10Ball_func(par): + """L10 Ball projection and proximal/dual proximal of related indicator + with sigma as callable""" + np.random.seed(10) + + l0 = L10Ball(3, lambda x: 1) + x = np.random.normal(0.0, 1.0, (3, par["nx"])).astype(par["dtype"]).ravel() + 1.0 + + # evaluation + assert l0(x) is False + xp = l0.prox(x, 1.0) + assert l0(xp) is True + + # prox / dualprox + tau = 2.0 + assert moreau(l0, x, tau) + + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L1Ball(par): """L1 Ball projection and proximal/dual proximal of related indicator""" diff --git a/pytests/test_proximal.py b/pytests/test_proximal.py index 679a57b..4b94aa8 100644 --- a/pytests/test_proximal.py +++ b/pytests/test_proximal.py @@ -21,29 +21,57 @@ par2 = {"nx": 11, "ny": 14, "sigma": 2.0, "dtype": "float64"} # odd float64 +def test_Quadratic_nonsquare(): + """Check Quadratic returns an error if the operator is not square""" + np.random.seed(10) + A = np.random.normal(0, 1, (10, 11)) + + with pytest.raises(ValueError, match="Op must be square"): + _ = Quadratic(Op=MatrixMult(A)) + + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_Quadratic(par): """Quadratic functional and proximal/dual proximal""" np.random.seed(10) - A = np.random.normal(0, 1, (par["nx"], par["nx"])) + A = np.random.normal(0, 1, (par["nx"], par["nx"])).astype(par["dtype"]) A = A.T @ A - quad = Quadratic(Op=MatrixMult(A), b=np.ones(par["nx"]), niter=500) + b = np.ones(par["nx"]).astype(par["dtype"]) - # prox / dualprox - tau = 2.0 - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) - assert moreau(quad, x, tau) + for explicit in [False, True]: + Op = MatrixMult(A) + Op.explicit = explicit + quad = Quadratic(Op=Op, b=b, niter=500) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + + # call + _ = quad(x) + + # grad + grad = A @ x + b + assert_array_almost_equal(quad.grad(x), grad, decimal=4) + + # prox / dualprox + tau = 2.0 + assert moreau(quad, x, tau) @pytest.mark.parametrize("par", [(par1), (par2)]) def test_DotProduct(par): """Dot product functional and proximal/dual proximal""" np.random.seed(10) - quad = Quadratic(b=np.ones(par["nx"])) + b = np.ones(par["nx"]).astype(par["dtype"]) + quad = Quadratic(b=b) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + + # call + _ = quad(x) + + # grad + assert_array_almost_equal(quad.grad(x), b, decimal=4) # prox / dualprox tau = 2.0 - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) assert moreau(quad, x, tau) @@ -52,10 +80,16 @@ def test_Constant(par): """Constant functional and proximal/dual proximal""" np.random.seed(10) quad = Quadratic(c=5.0) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + + # call + _ = quad(x) + + # grad + assert_array_almost_equal(quad.grad(x), 0.0, decimal=4) # prox / dualprox tau = 2.0 - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) assert moreau(quad, x, tau) @@ -186,6 +220,24 @@ def test_SingularValuePenalty(par): assert moreau(penalty, X.ravel(), tau) +def test_QuadraticEnvelopeCardIndicator(): + """QuadraticEnvelopeCardIndicator penalty for various edge cases""" + fr0 = QuadraticEnvelopeCardIndicator(5) + + # Check value for input size smaller than threshold + assert fr0(np.ones(2)) == 0 + + # Prox input size smaller than threshold + tau = 0.1 + x = np.ones(2) + assert_array_equal(fr0.prox(x, tau), x) + + # Prox for tau larger than 1.0 (hard-thresholding on tau) + tau = 2.0 + x = np.full(2, 5.0) + assert_array_equal(fr0.prox(x, tau), x) + + @pytest.mark.parametrize( "par,expected", [(par1, 94.89988856174841), (par2, 145.6421905545182)] ) @@ -193,6 +245,7 @@ def test_QuadraticEnvelopeCardIndicator_case01(par, expected): """QuadraticEnvelopeCardIndicator penalty and proximal/dual proximal""" np.random.seed(10) fr0 = QuadraticEnvelopeCardIndicator(4) + # Quadratic envelope of the indicator function of the l0-penalty x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) @@ -207,6 +260,7 @@ def test_QuadraticEnvelopeCardIndicator_case01(par, expected): def test_QuadraticEnvelopeCardIndicator_case02(): """QuadraticEnvelopeCardIndicator penalty and proximal/dual proximal""" fr0 = QuadraticEnvelopeCardIndicator(5) + # Quadratic envelope of the indicator function of the l0-penalty x = np.array([1, 1.5, 1.3, 4.1, 2.1, 1.6, 1.8, 1.8]) From dc6b1a80bb8ccb714a2e12f76fbfb41e548631c4 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 22 Feb 2026 21:51:15 +0000 Subject: [PATCH 53/72] test: increased test coverage --- pyproximal/proximal/L2.py | 36 +++-- pyproximal/proximal/Orthogonal.py | 2 +- pyproximal/proximal/RelaxedMS.py | 10 -- pyproximal/proximal/Simplex.py | 2 +- pyproximal/proximal/TV.py | 10 +- pyproximal/utils/gradtest.py | 2 + pytests/test_basic.py | 101 ++++++++++-- pytests/test_concave_penalties.py | 70 ++++++-- pytests/test_dykstra.py | 23 +++ pytests/test_grads.py | 4 +- pytests/test_norms.py | 261 +++++++++++++++++++++++++----- pytests/test_projection.py | 65 +++++++- pytests/test_proximal.py | 83 ++++++++-- pytests/test_solver.py | 14 +- 14 files changed, 553 insertions(+), 130 deletions(-) diff --git a/pyproximal/proximal/L2.py b/pyproximal/proximal/L2.py index 2fb271b..76d474e 100644 --- a/pyproximal/proximal/L2.py +++ b/pyproximal/proximal/L2.py @@ -5,7 +5,11 @@ from pylops import Identity, MatrixMult from pylops.optimization.basic import cg, lsqr from pylops.optimization.leastsquares import regularized_inversion -from pylops.utils.backend import get_array_module, get_module_name +from pylops.utils.backend import ( + get_array_module, + get_module_name, + get_normalize_axis_index, +) from pylops.utils.typing import NDArray, ShapeLike from scipy.linalg import cho_factor, cho_solve from scipy.sparse.linalg import lsqr as sp_lsqr @@ -351,23 +355,31 @@ def __init__( self.nfft = nfft self.sigma = sigma self.dims = dims - self.dir = -1 if dir is None else dir + if dims is None: + self.dir = -1 + else: + self.dir = ( + len(self.dims) - 1 + if dir is None + else get_normalize_axis_index()(dir, len(self.dims)) + ) # convert data and filter to Fourier domain self.bf = np.fft.fft(b, self.nfft, axis=self.dir) - self.hf = np.fft.fft(h, self.nfft, axis=self.dir) + self.hf = np.fft.fft(h, self.nfft, axis=0 if h.ndim == 1 else self.dir) # expand dimensions of filters if self.dims is not None: - self.bf = self.bf.reshape(self.dims) self.dimsf = list(self.dims).copy() self.dimsf[self.dir] = nfft + self.bf = self.bf.reshape(self.dimsf) ndims = len(self.dims) - for _ in range(self.dir - 1): - self.hf = np.expand_dims(self.hf, axis=0) - for _ in range(ndims - self.dir - 1): - self.hf = np.expand_dims(self.hf, axis=-1) + if self.hf.ndim == 1: + for _ in range(self.dir): + self.hf = np.expand_dims(self.hf, axis=0) + for _ in range(ndims - self.dir - 1): + self.hf = np.expand_dims(self.hf, axis=-1) # precompute terms for prox self.hbf = np.conj(self.hf) * self.bf @@ -393,14 +405,16 @@ def prox(self, x: NDArray, tau: float) -> NDArray: y = y[: len(x)] else: y = np.take(y, range(self.dims[self.dir]), axis=self.dir).ravel() - return y.ravel() + return y def grad(self, x: NDArray) -> NDArray: if self.dims is not None: x = x.reshape(self.dims) xf = np.fft.fft(x, self.nfft, axis=self.dir) - f = self.sigma * np.fft.ifft( + g = self.sigma * np.fft.ifft( np.conj(self.hf) * (self.hf * xf - self.bf), axis=self.dir ) - return f.ravel() + g = np.take(g, range(x.shape[self.dir]), axis=self.dir).ravel() + + return g diff --git a/pyproximal/proximal/Orthogonal.py b/pyproximal/proximal/Orthogonal.py index 479860b..aa9bfa0 100644 --- a/pyproximal/proximal/Orthogonal.py +++ b/pyproximal/proximal/Orthogonal.py @@ -73,7 +73,7 @@ def __init__( self.Q = Q self.partial = partial self.alpha = alpha - self.b = b if b is not None else 0 + self.b = b if b is not None else 0.0 def __call__(self, x: NDArray) -> bool | float | int: y = self.Q.matvec(x) diff --git a/pyproximal/proximal/RelaxedMS.py b/pyproximal/proximal/RelaxedMS.py index 92ac1de..e6047c7 100644 --- a/pyproximal/proximal/RelaxedMS.py +++ b/pyproximal/proximal/RelaxedMS.py @@ -32,16 +32,6 @@ def _l2(x: NDArray, alpha: float) -> NDArray: return y -def _current_kappa( - kappa: FloatCallableLike, - count: int, -) -> float | NDArray: - if not callable(kappa): - return kappa - else: - return kappa(count) - - class RelaxedMumfordShah(ProxOperator): r"""Relaxed Mumford-Shah norm proximal operator. diff --git a/pyproximal/proximal/Simplex.py b/pyproximal/proximal/Simplex.py index a6c40dd..6a5a3ee 100644 --- a/pyproximal/proximal/Simplex.py +++ b/pyproximal/proximal/Simplex.py @@ -68,7 +68,7 @@ def __call__(self, x: NDArray, tol: float = 1e-8) -> bool: carr = np.empty(self.dims[self.otheraxis], dtype=np.bool) for i in range(self.dims[self.otheraxis]): carr[i] = not ( - np.abs(np.sum(x)) - self.radius < tol or np.any(x[i] < 0) + np.abs(np.sum(x[i])) - self.radius > tol or np.any(x[i] < 0) ) c = bool(np.all(carr)) return c diff --git a/pyproximal/proximal/TV.py b/pyproximal/proximal/TV.py index bf32148..913f28a 100644 --- a/pyproximal/proximal/TV.py +++ b/pyproximal/proximal/TV.py @@ -18,7 +18,7 @@ class TV(ProxOperator): Parameters ---------- - dims : :obj:`tuple` + dims : :obj:`tuple`, optional Number of samples for each dimension (``None`` if only one dimension is available) sigma : :obj:`float`, optional @@ -41,7 +41,7 @@ class TV(ProxOperator): def __init__( self, - dims: ShapeLike, + dims: ShapeLike | None, sigma: float = 1.0, niter: int | Callable[[int], int] = 10, rtol: float = 1e-4, @@ -49,7 +49,7 @@ def __init__( ) -> None: super().__init__(None, True) self.dims = dims - self.ndim = len(dims) + self.ndim = 1 if dims is None else len(dims) self.sigma = sigma self.niter = niter self.count = 0 @@ -185,10 +185,6 @@ def prox(self, x: NDArray, tau: float) -> NDArray: iter = 0 while iter <= niter: # Current Solution - if self.ndim == 0: - msg = "Need to input at least one value" - raise ValueError(msg) - if self.ndim >= 1: div = np.concatenate( ( diff --git a/pyproximal/utils/gradtest.py b/pyproximal/utils/gradtest.py index d3e48be..96ec9c7 100644 --- a/pyproximal/utils/gradtest.py +++ b/pyproximal/utils/gradtest.py @@ -220,6 +220,8 @@ def gradtest_bilinear( r_or_i = np.random.randint(0, 2) if r_or_i == 1: delta1 = delta * 1j + else: + r_or_i = 0 # extract gradient value to test if x_or_y == 0: diff --git a/pytests/test_basic.py b/pytests/test_basic.py index d826e30..d1fce90 100644 --- a/pytests/test_basic.py +++ b/pytests/test_basic.py @@ -3,6 +3,7 @@ from numpy.testing import assert_array_equal from pylops import MatrixMult +from pyproximal import ProxOperator from pyproximal.proximal import L1, L2, Euclidean, Quadratic par1 = {"ny": 21, "nx": 11, "nt": 20, "imag": 0, "dtype": "float32"} # real @@ -11,26 +12,43 @@ np.random.seed(10) +@pytest.mark.parametrize("par", [par1]) +def test_ProxOperator(par): + """Check errors are raised for non-implemented methods""" + pop = ProxOperator() + + with pytest.raises( + NotImplementedError, match="This ProxOperator's __call__ method" + ): + pop(np.ones(4)) + + @pytest.mark.parametrize("par", [par1]) def test_negativetau(par): """Check error is raised when passing negative tau""" l1 = Euclidean(sigma=1.0) x = np.arange(-5, 5, 0.1) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="tau must be positive"): l1.prox(x, -1) @pytest.mark.parametrize("par", [par1]) -def test_add(par): - """Check __add__ operator against one proximal operator to which we - can add a dot-product (e.g., Quadratic) +@pytest.mark.parametrize("sign", [-1, 1]) +def test_add_sub(par, sign): + """Check __add__ and __sub__ operators against one proximal + operator to which we can add a dot-product (e.g., Quadratic) """ + np.random.seed(10) + A = np.random.normal(0, 1, (par["nx"], par["nx"])) A = A.T @ A - quad = Quadratic(Op=MatrixMult(A), b=np.ones(par["nx"]), niter=500) + quad = Quadratic(Op=MatrixMult(A), b=sign * np.ones(par["nx"]), niter=500) quad1 = Quadratic(Op=MatrixMult(A), niter=500) - quad1 = quad1 + np.ones(par["nx"]) + if sign == 1: + quad1 = quad1 + np.ones(par["nx"]) + else: + quad1 = quad1 - np.ones(par["nx"]) # prox / dualprox x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) @@ -44,6 +62,8 @@ def test_mul(par): """Check __mul__ operator against one proximal operator to which we can multiply a scalar (e.g., L2) """ + np.random.seed(10) + sigma = 2.0 l2 = L2(sigma=sigma) l21 = sigma * L2() @@ -56,13 +76,43 @@ def test_mul(par): @pytest.mark.parametrize("par", [par1]) -def test_precomposition_type(par): - """Check precomposition method raises an error +def test_adjoint(par): + """Check _adjoint operator against one proximal operator + that implements proxdual + """ + np.random.seed(10) + + sigma = 2.0 + l1 = L1(sigma=sigma) + l1H = l1.H + + # prox / dualprox + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + tau = 2.0 + assert_array_equal(l1.proxdual(x, tau), l1H.prox(x, tau)) + assert_array_equal(l1.prox(x, tau), l1H.proxdual(x, tau)) + + +@pytest.mark.parametrize("par", [par1]) +def test_precomposition(par): + """Check precomposition method and that it raises an error when type of a and b is incorrect """ + np.random.seed(10) + l1 = L1() + a, b = 1.0, 5.0 + l1prec = l1.precomposition(a=a, b=b) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + + # norm + assert l1prec(x) == l1prec.f(a * x + b) - with pytest.raises(NotImplementedError): + # grad + assert_array_equal(l1prec.grad(x), a * l1.grad(a * x + b)) + + # check errors + with pytest.raises(NotImplementedError, match="a must be of type float and b"): _ = l1.precomposition(a=1, b=np.ones(5)) # should be float _ = l1.precomposition( @@ -72,13 +122,25 @@ def test_precomposition_type(par): @pytest.mark.parametrize("par", [par1]) -def test_postcomposition_type(par): - """Check postcomposition method raises an error +def test_postcomposition(par): + """Check postcomposition method and that it raises an error when type of sigma is incorrect """ + np.random.seed(10) + l1 = L1() + sigma = 2.0 + l1post = l1.postcomposition(sigma=sigma) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + + # norm + assert l1post(x) == sigma * l1(x) - with pytest.raises(NotImplementedError): + # grad + assert_array_equal(l1post.grad(x), sigma * l1.grad(x)) + + # check errors + with pytest.raises(NotImplementedError, match="sigma must be of type float"): _ = l1.postcomposition( sigma=1, # should be float ) @@ -89,12 +151,23 @@ def test_postcomposition_type(par): @pytest.mark.parametrize("par", [par1]) def test_affine_addition_type(par): - """Check affine_addition method raises an error + """Check affine_addition method and that it raises an error when type of v is incorrect """ l1 = L1() - with pytest.raises(NotImplementedError): + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + v = np.ones(par["nx"]).astype(par["dtype"]) + l1aff = l1 + v + + # norm + assert l1aff(x) == l1(x) + np.dot(v, x) + + # grad + assert_array_equal(l1aff.grad(x), l1.grad(x) + v) + + # error + with pytest.raises(NotImplementedError, match="v must be a numpy.ndarray"): _ = l1.affine_addition( v=1, # should be np.ndarray or cp.ndarray ) diff --git a/pytests/test_concave_penalties.py b/pytests/test_concave_penalties.py index 1db568a..b850fa1 100644 --- a/pytests/test_concave_penalties.py +++ b/pytests/test_concave_penalties.py @@ -22,6 +22,15 @@ } # odd float64 +def test_SCAD_sigmaneg(): + """Check SCAD returns an error for negative sigma""" + with pytest.raises(ValueError, match='Variable "sigma" must be positive'): + _ = SCAD(sigma=-1.0) + + with pytest.raises(ValueError, match='Variable "a" must be larger'): + _ = SCAD(sigma=1.0, a=1.7) + + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_SCAD(par): """SCAD penalty and proximal/dual proximal""" @@ -37,23 +46,33 @@ def test_SCAD(par): (par["a"] + 1) * par["sigma"] ** 2 / 2 * np.count_nonzero(x) ) - # Check proximal operator + # prox / dualprox tau = 2.0 x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) assert moreau(scad, x, tau) - # Make sure that a ValueError is raised if you try to instantiate with a <= 2 - with pytest.raises(ValueError): - _ = SCAD(sigma=1.0, a=1.7) + +def test_Log_Log1_neg(): + """Check Log/Log1 returns an error for negative inputs""" + with pytest.raises(ValueError, match='Variable "sigma" must be positive'): + _ = Log(sigma=-1.0) + + with pytest.raises(ValueError, match='Variable "gamma" must be positive'): + _ = Log(sigma=1.0, gamma=-1.0) + + with pytest.raises(ValueError, match='Variable "delta" must be positive'): + _ = Log1(1.0, delta=-1.0) @pytest.mark.parametrize("par", [(par1), (par2)]) def test_Log(par): """Log penalty and proximal/dual proximal""" np.random.seed(10) + log = Log(sigma=par["sigma"], gamma=par["gamma"]) - # Log x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) + + # norm expected = ( par["sigma"] / np.log(par["gamma"] + 1) @@ -61,7 +80,7 @@ def test_Log(par): ) assert log(x) == pytest.approx(expected) - # Check proximal operator + # prox / dualprox tau = 2.0 assert moreau(log, x, tau) @@ -70,21 +89,37 @@ def test_Log(par): def test_Log1(par): """Log1 penalty and proximal/dual proximal""" np.random.seed(10) + log = Log1(sigma=par["sigma"]) + x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) - # Check proximal operator + # norm + expected = float(np.sum(np.log(np.abs(x) + log.delta))) + assert log(x) == pytest.approx(expected) + + # prox / dualprox tau = 2.0 - x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) assert moreau(log, x, tau) +def test_ETP_sigma_gamma_neg(): + """Check ETP returns an error for negative sigma and gamma""" + with pytest.raises(ValueError, match='Variable "sigma" must be positive'): + _ = ETP(sigma=-1.0) + + with pytest.raises(ValueError, match='Variable "gamma" must be strictly'): + _ = ETP(sigma=1.0, gamma=0.0) + + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_ETP(par): """Exponential-type penalty and proximal/dual proximal""" np.random.seed(10) + etp = ETP(sigma=par["sigma"], gamma=par["gamma"]) - # ETP x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) + + # norm expected = ( par["sigma"] / (1 - np.exp(-par["gamma"])) @@ -92,24 +127,33 @@ def test_ETP(par): ) assert etp(x) == pytest.approx(expected) - # Check proximal operator + # prox / dualprox tau = 2.0 assert moreau(etp, x, tau) +def test_Geman_sigma_gamma_neg(): + """Check Geman returns an error for negative sigma and gamma""" + with pytest.raises(ValueError, match='Variable "sigma" must be positive'): + _ = Geman(sigma=-1.0) + + with pytest.raises(ValueError, match='Variable "gamma" must be strictly'): + _ = Geman(sigma=1.0, gamma=0.0) + + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_Geman(par): """Geman penalty and proximal/dual proximal""" np.random.seed(10) geman = Geman(sigma=par["sigma"], gamma=par["gamma"]) - - # Geman x = np.random.normal(0.0, 10.0, par["nx"]).astype(par["dtype"]) + + # norm expected = par["sigma"] * np.linalg.norm(np.abs(x) / (np.abs(x) + par["gamma"]), 1) assert geman(x) == pytest.approx(expected) - # Check proximal operator + # prox / dualprox tau = 2.0 assert moreau(geman, x, tau) diff --git a/pytests/test_dykstra.py b/pytests/test_dykstra.py index 5dfdedb..2a68be0 100644 --- a/pytests/test_dykstra.py +++ b/pytests/test_dykstra.py @@ -107,6 +107,29 @@ def test_GenericIntersectionProx(par: dict[str, Any]) -> None: assert moreau(d, x, tau) +@pytest.mark.parametrize("par", [(par1prox), (par2prox)]) +def test_Sum_l1(par: dict[str, Any]) -> None: + """Check Sum for single L1 operator (edge-case)""" + + atol = 1e-6 + tau = 1.0 + rng = np.random.default_rng(10) + + x = rng.normal(0.0, 3.5, par["nx"]).astype(par["dtype"]) + sigma = rng.uniform(0.1, 1.0) + + l1 = L1(sigma=sigma) + + d = Sum( + [ + l1, + ] + ) + assert np.allclose(l1(x), d(x), atol=atol) + assert np.allclose(l1.prox(x, tau), d.prox(x, tau), atol=atol) + assert moreau(d, x, tau) + + @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) def test_Sum_l1_l1(par: dict[str, Any]) -> None: """Check Sum for L1 + L1""" diff --git a/pytests/test_grads.py b/pytests/test_grads.py index 6454070..cefa626 100644 --- a/pytests/test_grads.py +++ b/pytests/test_grads.py @@ -40,7 +40,7 @@ def test_l2(par): complexflag=par["complexflag"], raiseerror=True, atol=1e-3, - verb=False, + verb=True, # to test verbosity ) # x - b @@ -94,5 +94,5 @@ def test_lowrank(par): complexflag=par["complexflag"], raiseerror=True, atol=1e-3, - verb=False, + verb=True, # to test verbosity ) diff --git a/pytests/test_norms.py b/pytests/test_norms.py index a0ff4e5..65c2ebb 100644 --- a/pytests/test_norms.py +++ b/pytests/test_norms.py @@ -1,7 +1,14 @@ import numpy as np import pytest from numpy.testing import assert_array_almost_equal -from pylops.basicoperators import Diagonal, FirstDerivative, Identity, MatrixMult +from pylops.basicoperators import ( + Diagonal, + FirstDerivative, + Gradient, + Identity, + MatrixMult, +) +from pylops.signalprocessing import Convolve1D from pyproximal.proximal import ( L0, @@ -12,6 +19,7 @@ Euclidean, Huber, HuberCircular, + L2Convolve, L21_plus_L1, Nuclear, RelaxedMumfordShah, @@ -38,26 +46,36 @@ def test_Euclidean(par): # grad assert_array_almost_equal(eucl.grad(x), par["sigma"] * x / np.linalg.norm(x)) - # prox / dualprox + # prox / dualprox (checking also verbosity) tau = 2.0 - assert moreau(eucl, x, tau) + assert moreau(eucl, x, tau, verb=True) + + +def test_L2_solver(): + """Check L2 raises an error if the solver is not recognized""" + with pytest.raises(ValueError, match="Available options are:"): + d = np.random.normal(0.0, 1.0, 10) + _ = L2(Op=Diagonal(d), solver="foo") @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_L2_op(par): - """L2 norm of Op*x and proximal (since Op is a Diagonal - operator the denominator becomes 1 + sigma*tau*d[i]^2 - for every i)""" +@pytest.mark.parametrize("niter", [500, lambda x: 500]) +def test_L2_op(par, niter): + """L2 norm of Op*x and grad and proximal""" np.random.seed(10) b = np.zeros(par["nx"], dtype=par["dtype"]) d = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) - l2 = L2(Op=Diagonal(d, dtype=par["dtype"]), b=b, sigma=par["sigma"], niter=500) + l2 = L2(Op=Diagonal(d, dtype=par["dtype"]), b=b, sigma=par["sigma"], niter=niter) # norm x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) assert l2(x) == (par["sigma"] / 2.0) * np.linalg.norm(d * x) ** 2 + # grad: since Op is a Diagonal operator the gradient becomes + # -sigma*d[i]*(b[i] - d[i]*x[i]) for every i + assert_array_almost_equal(l2.grad(x), -par["sigma"] * d * (b - d * x)) + # prox: since Op is a Diagonal operator the denominator becomes # 1 + sigma*tau*d[i] for every i tau = 2.0 @@ -66,32 +84,44 @@ def test_L2_op(par): @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_L2_op_solver(par): +@pytest.mark.parametrize("explicit", [False, True]) +def test_L2_op_solver(par, explicit): """L2 norm of Op*x-b and proximal, the first compared to close-form solution and the second with different choices of solver.""" np.random.seed(10) - Op = MatrixMult( - np.random.normal(0, 1, (par["nx"], par["nx"])).astype(dtype=par["dtype"]), - dtype=par["dtype"], - ) - b = np.ones(par["nx"], dtype=par["dtype"]) - l2_leg = L2(Op=Op, b=b, sigma=par["sigma"], solver="legacy", niter=par["nx"]) - l2_cg = L2(Op=Op, b=b, sigma=par["sigma"], solver="cg", niter=par["nx"]) - l2_cgls = L2(Op=Op, b=b, sigma=par["sigma"], solver="cgls", niter=par["nx"]) + for q in (None, np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"])): + Op = MatrixMult( + np.random.normal(0, 1, (par["nx"], par["nx"])).astype(dtype=par["dtype"]), + dtype=par["dtype"], + ) + Op.explicit = explicit + b = np.ones(par["nx"], dtype=par["dtype"]) + l2_leg = L2( + Op=Op, b=b, sigma=par["sigma"], q=q, solver="legacy", niter=2 * par["nx"] + ) + l2_cg = L2( + Op=Op, b=b, sigma=par["sigma"], q=q, solver="cg", niter=2 * par["nx"] + ) + l2_cgls = L2( + Op=Op, b=b, sigma=par["sigma"], q=q, solver="cgls", niter=2 * par["nx"] + ) - # norm - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) - assert l2_leg(x) == (par["sigma"] / 2.0) * np.linalg.norm(Op * x - b) ** 2 + # norm + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + norm = (par["sigma"] / 2.0) * np.linalg.norm(Op * x - b) ** 2 + if q is not None: + norm += np.dot(q, x) + assert l2_leg(x) == norm - # prox - tau = 2.0 - prox_leg = l2_leg.prox(x, tau) - prox_cg = l2_cg.prox(x, tau) - prox_cgls = l2_cgls.prox(x, tau) + # prox + tau = 2.0 + prox_leg = l2_leg.prox(x, tau) + prox_cg = l2_cg.prox(x, tau) + prox_cgls = l2_cgls.prox(x, tau) - assert_array_almost_equal(prox_leg, prox_cg, decimal=4) - assert_array_almost_equal(prox_leg, prox_cgls, decimal=4) + assert_array_almost_equal(prox_leg, prox_cg, decimal=4) + assert_array_almost_equal(prox_leg, prox_cgls, decimal=4) @pytest.mark.parametrize("par", [(par1), (par2)]) @@ -144,34 +174,148 @@ def test_L2_diff(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L2_x(par): - """L2 norm of x and proximal (implemented both directly and + """L2 norm of x and grad and proximal (implemented both directly and with identity operator and zero b and compared to closed-form solution)""" np.random.seed(10) - l2 = L2( - Op=Identity(par["nx"], dtype=par["dtype"]), - b=np.zeros(par["nx"], dtype=par["dtype"]), + for q in (None, np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"])): + l2 = L2( + Op=Identity(par["nx"], dtype=par["dtype"]), + b=np.zeros(par["nx"], dtype=par["dtype"]), + sigma=par["sigma"], + q=q, + ) + l2direct = L2( + sigma=par["sigma"], + q=q, + ) + + # norm + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + norm = (par["sigma"] / 2.0) * np.linalg.norm(x) ** 2 + if q is not None: + norm += np.dot(q, x) + assert l2(x) == norm + assert l2direct(x) == norm + + # grad + grad = par["sigma"] * x + if q is not None: + grad += q + assert_array_almost_equal(l2.grad(x), grad) + + # prox + tau = 2.0 + if q is not None: + prox = (x - tau * q) / (1.0 + par["sigma"] * tau) + else: + prox = x / (1.0 + par["sigma"] * tau) + assert_array_almost_equal(l2.prox(x, tau), prox) + assert_array_almost_equal(l2direct.prox(x, tau), prox) + + +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_L2Convolve(par): + """L2Convolve norm of x and grad and prox/dualprox (norm and grad are compared + to solution in the time domain)""" + np.random.seed(10) + + # 1d + h = np.ones(3) / 3.0 + Cop = Convolve1D(par["nx"], h=h, offset=1, dtype=par["dtype"]) + b = np.zeros(par["nx"], dtype=par["dtype"]) + + l2 = L2Convolve( + h=h, + b=b, + sigma=par["sigma"], + ) + + # define x with a spike in the middle to + # avoid boundary effects in the convolution + # in the time vs frequency domain + x = np.zeros(par["nx"], dtype=par["dtype"]) + x[par["nx"] // 2] = 1.0 + + # norm + norm = (par["sigma"] / 2.0) * np.linalg.norm(b - Cop @ x) ** 2 + assert l2(x) == pytest.approx(norm) + + # grad + grad = par["sigma"] * Cop.H @ (Cop @ x - b) + assert_array_almost_equal(l2.grad(x), grad) + + # prox / dualprox + tau = 2.0 + moreau(l2, x, tau) + + # 2d on first + h = np.ones(3) / 3.0 + Cop = Convolve1D((par["nx"], 4), h=h, offset=1, axis=0, dtype=par["dtype"]) + b = np.zeros((par["nx"], 4), dtype=par["dtype"]) + + l2 = L2Convolve( + h=h, + b=b, sigma=par["sigma"], + dims=(par["nx"], 4), + dir=0, ) - l2direct = L2( + + # define x with a spike in the middle to + # avoid boundary effects in the convolution + # in the time vs frequency domain + x = np.zeros((par["nx"], 4), dtype=par["dtype"]) + x[par["nx"] // 2] = 1.0 + + # norm + norm = (par["sigma"] / 2.0) * np.linalg.norm(b - Cop @ x) ** 2 + assert l2(x) == pytest.approx(norm) + + # grad + grad = par["sigma"] * Cop.H @ (Cop @ x - b) + assert_array_almost_equal(l2.grad(x).reshape(par["nx"], 4), grad) + + # prox / dualprox + tau = 2.0 + moreau(l2, x.ravel(), tau) + + # 2d on last + h = np.ones(3) / 3.0 + Cop = Convolve1D((4, par["nx"]), h=h, offset=1, axis=-1, dtype=par["dtype"]) + b = np.zeros((4, par["nx"]), dtype=par["dtype"]) + + l2 = L2Convolve( + h=h, + b=b, sigma=par["sigma"], + dims=(4, par["nx"]), + dir=-1, ) + # define x with a spike in the middle to + # avoid boundary effects in the convolution + # in the time vs frequency domain + x = np.zeros((4, par["nx"]), dtype=par["dtype"]) + x[:, par["nx"] // 2] = 1.0 + # norm - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) - assert l2(x) == (par["sigma"] / 2.0) * np.linalg.norm(x) ** 2 - assert l2direct(x) == (par["sigma"] / 2.0) * np.linalg.norm(x) ** 2 + norm = (par["sigma"] / 2.0) * np.linalg.norm(b - Cop @ x) ** 2 + assert l2(x) == pytest.approx(norm) - # prox + # grad + grad = par["sigma"] * Cop.H @ (Cop @ x - b) + assert_array_almost_equal(l2.grad(x).reshape(4, par["nx"]), grad) + + # prox / dualprox tau = 2.0 - assert_array_almost_equal(l2.prox(x, tau), x / (1.0 + par["sigma"] * tau)) - assert_array_almost_equal(l2direct.prox(x, tau), x / (1.0 + par["sigma"] * tau)) + moreau(l2, x.ravel(), tau) @pytest.mark.parametrize("par", [(par1), (par2)]) def test_L1(par): - """L1 norm and proximal/dual proximal""" + """L1 norm and grad and proximal/dual proximal""" np.random.seed(10) l1 = L1(sigma=par["sigma"]) @@ -180,6 +324,11 @@ def test_L1(par): x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) assert l1(x) == par["sigma"] * np.sum(np.abs(x)) + # grad (note that since this norm is non-smooth, the + # gradient method is not implemented; as such the gradient + # of the Moreau envelope is called instead + _ = l1.grad(x) + # prox / dualprox tau = 2.0 assert moreau(l1, x, tau) @@ -316,27 +465,51 @@ def test_HuberCircular(par): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_TV(par): - """TV norm of x and proximal""" + """TV norm of x and proximal/dual proximal""" np.random.seed(10) + # 1d tv = TV(dims=(par["nx"],), sigma=par["sigma"]) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) # norm - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) derivOp = FirstDerivative(par["nx"], dtype=par["dtype"], kind="forward") dx = derivOp @ x assert_array_almost_equal(tv(x), par["sigma"] * np.sum(np.abs(dx), axis=0)) + # prox / dualprox + tau = 2.0 + assert moreau(tv, x, tau) + + # 2d/3d/4d + dims = ((4, par["nx"]), (4, 8, par["nx"]), (4, 4, 8, par["nx"])) + + for dim in dims: + tv = TV(dims=dim, sigma=par["sigma"]) + x = np.random.normal(0.0, 1.0, dim).astype(par["dtype"]) + + # norm + derivOp = Gradient(dim, dtype=par["dtype"], kind="forward") + dx = derivOp @ x + f = 0.0 + for g in dx: + f += np.power(abs(g), 2) + assert_array_almost_equal(tv(x), par["sigma"] * np.sum(np.sqrt(f))) + + # prox / dualprox + tau = 2.0 + assert moreau(tv, x.ravel(), tau) + @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_rMS(par): +@pytest.mark.parametrize("kappa", [1.0, lambda x: 1.0]) +def test_rMS(par, kappa): """rMS norm and proximal/dual proximal""" - kappa = 1.0 rMS = RelaxedMumfordShah(sigma=par["sigma"], kappa=kappa) # norm x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) - assert rMS(x) == np.minimum(par["sigma"] * np.linalg.norm(x) ** 2, kappa) + assert rMS(x) == np.minimum(par["sigma"] * np.linalg.norm(x) ** 2, 1.0) # prox / dualprox tau = 2.0 diff --git a/pytests/test_projection.py b/pytests/test_projection.py index 9e22279..2473d8a 100644 --- a/pytests/test_projection.py +++ b/pytests/test_projection.py @@ -9,6 +9,7 @@ EuclideanBall, HalfSpace, Hankel, + Intersection, L0Ball, L1Ball, L10Ball, @@ -197,6 +198,16 @@ def test_Simplex(par): assert moreau(sim1, x, tau) +def test_Simplex_engine(): + """Simplex engine check""" + with pytest.raises(KeyError, match="engine must be numpy or "): + _ = Simplex( + n=10, + radius=1.0, + engine="foo", + ) + + @pytest.mark.parametrize("par", [(par1), (par2), (par3), (par4)]) def test_Simplex_multi(par): """Simplex projection and proximal/dual proximal for 2d array""" @@ -207,21 +218,34 @@ def test_Simplex_multi(par): for engine in ["numpy", "numba"]: x = np.abs(np.random.normal(0.0, 1.0, dims).astype(par["dtype"])) + radius = np.sum(x, axis=par["axis"]).max() sim = Simplex( n=par["ny"] * par["nx"], - radius=5.0, + radius=radius, + dims=dims, + axis=par["axis"], + maxiter=50, + engine=engine, + ) + sim1 = Simplex( + n=par["ny"] * par["nx"], + radius=radius - 0.1, dims=dims, axis=par["axis"], maxiter=50, engine=engine, ) + # evaluation + assert sim(x) is True + assert sim1(x) is False + # prox tau = 2.0 y = sim.prox(x.ravel(), tau) assert_array_almost_equal( np.sum(y.reshape(dims), axis=par["axis"]), - 5.0 * np.ones(dims[otheraxis]), + radius * np.ones(dims[otheraxis]), decimal=1, ) @@ -235,12 +259,16 @@ def test_Affine(par): np.random.seed(10) Op = Identity(par["nx"]) - b = np.random.normal(0.0, 1.0, par["nx"]) + x = np.ones(par["nx"]) + b = Op @ x aff = AffineSet(Op, b, 10) + # norm + assert aff(x) is True + assert aff(x + 1.0) is False + # prox tau = 2.0 - x = np.ones(par["nx"]) assert_array_almost_equal(aff.prox(x, tau), b) # prox / dualprox @@ -271,11 +299,36 @@ def test_HalfSpace(par): np.random.seed(10) w = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) - b = np.random.normal(0.0, 1.0) + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + b = np.dot(w, x) + 1.0 # to ensure x is inside the half-space half_space = HalfSpace(w, b) - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + + # call + assert half_space(x) is True # prox / dualprox tau = 2.0 assert moreau(half_space, x, tau) + + +@pytest.mark.parametrize("par", [(par1), (par2)]) +def test_Intersection(par): + """Intersection projection and proximal/dual proximal of related indicator""" + np.random.seed(10) + + k = 3 + x = np.random.normal(0, k, par["nx"]) + sigma = np.array([[3, 2, 1], [2, 2, 1], [3, 4, 1]]) + sigma = sigma.T @ sigma + + ic = Intersection(k, par["nx"], sigma, niter=10, tol=1e-3) + x = np.random.normal(0.0, 1.0, k * par["nx"]).astype(par["dtype"]) + + # call + xproj = ic.prox(x, 2.0) + assert ic(xproj) is True + + # prox / dualprox + tau = 2.0 + assert moreau(ic, x, tau) diff --git a/pytests/test_proximal.py b/pytests/test_proximal.py index 4b94aa8..6fe1372 100644 --- a/pytests/test_proximal.py +++ b/pytests/test_proximal.py @@ -98,13 +98,16 @@ def test_SemiOrthogonal(par): """L1 functional with Semi-Orthogonal operator and proximal/dual proximal""" np.random.seed(10) l1 = L1() - orth = Orthogonal( - l1, 2 * Identity(par["nx"]), b=np.arange(par["nx"]), partial=True, alpha=4.0 - ) + Op = 2 * Identity(par["nx"]) + b = np.arange(par["nx"]) + orth = Orthogonal(l1, Op, b=b, partial=True, alpha=4.0) + + # norm + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + assert orth(x) == pytest.approx(l1(Op @ x + b)) # prox / dualprox tau = 2.0 - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) assert moreau(orth, x, tau) @@ -113,25 +116,48 @@ def test_Orthogonal(par): """L1 functional with Orthogonal operator and proximal/dual proximal""" np.random.seed(10) l1 = L1() - orth = Orthogonal(l1, Identity(par["nx"]), b=np.arange(par["nx"])) + Op = Identity(par["nx"], inplace=False) + b = np.arange(par["nx"]) + orth = Orthogonal(l1, Op, b=b) + + # norm + x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) + assert orth(x) == pytest.approx(l1(Op @ x + b)) # prox / dualprox tau = 2.0 - x = np.random.normal(0.0, 1.0, par["nx"]).astype(par["dtype"]) assert moreau(orth, x, tau) +def test_VStack_nodim(): + """VStack operator raises error when neiter restr not dim are provided""" + with pytest.raises(ValueError, match="Provide either nn or restr"): + _ = VStack( + [ + L2(), + ], + None, + None, + ) + + @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_VStack_error(par): - """VStack operator error when input has wrong dimensions""" +def test_VStack_wrongdim(par): + """VStack call/prox/grad raise error when input has wrong dimensions""" np.random.seed(10) nxs = [par["nx"] // 4] * 4 nxs[-1] = par["nx"] - np.sum(nxs[:-1]) l2 = L2() vstack = VStack([l2] * 4, nxs) - with pytest.raises(ValueError): - vstack.prox(np.ones(nxs[0]), 2) + with pytest.raises(ValueError, match="x must have size"): + vstack(np.ones(nxs[0])) + + with pytest.raises(ValueError, match="x must have size"): + vstack.prox(np.ones(nxs[0]), 2.0) + + with pytest.raises(ValueError, match="x must have size"): + vstack.grad(np.ones(nxs[0])) @pytest.mark.parametrize("par", [(par1), (par2)]) @@ -194,13 +220,42 @@ def test_VStack_restriction(par): def test_Nonlinear(): - """Nonlinear proximal operator. Since this is a template class simply check - that errors are raised when not used properly - """ + """Nonlinear proximal operator.""" np.random.seed(10) - with pytest.raises(TypeError): + + # Check error if methods are not implemented + with pytest.raises(TypeError, match="Can't instantiate abstract"): _ = Nonlinear(np.ones(10)) + # Implement basic nonlinear function and test methods + class Sin(Nonlinear): + def fun(self, x): + return 1 - np.sin(np.pi * x) + + def grad(self, x): + return -np.pi * np.cos(np.pi * x) + + def optimize(self): + x = self.x0.copy() + for _ in range(self.niter): + _ = self._fungradprox(x, self.tau) # just to test the method + dx = self._gradprox(x, self.tau) + x -= self.alpha * dx + return x + + sin = Sin(x0=np.full(1, 0.2), niter=100) + sin.alpha = 1e-1 + + # call + assert sin(0.5) == 0.0 + assert sin(0.0) == 1.0 + + # call and grad + assert sin.fungrad(0.5)[1] == pytest.approx(0.0) + + # prox + assert sin.prox(np.full(1, 0.5), 1e5)[0] == pytest.approx(0.5) + @pytest.mark.parametrize("par", [(par1), (par2)]) def test_SingularValuePenalty(par): diff --git a/pytests/test_solver.py b/pytests/test_solver.py index b0649ef..8ffce8f 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -26,7 +26,7 @@ def test_HQS_noinitial(): """Check that an error is raised if no initial value is provided to HQS solver """ - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Both x0 or "): _ = HQS( proxf=L2(), proxg=L1(), @@ -40,7 +40,7 @@ def test_ADMM_noinitial(): """Check that an error is raised if no initial value is provided to ADMM solver """ - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Both x0 or"): _ = ADMM( proxf=L2(), proxg=L1(), @@ -54,7 +54,7 @@ def test_ADMML2_noinitial(): """Check that an error is raised if no initial value is provided to PrimalDual solver """ - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Both x0 or"): # Both None _ = ADMML2( proxg=L1(), @@ -65,7 +65,7 @@ def test_ADMML2_noinitial(): x0=None, z0=None, ) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="x0 must be provided when"): # x0 is None (and Op provided) _ = ADMML2( proxg=L1(), @@ -82,7 +82,7 @@ def test_LinearizedADMM_noinitial(): """Check that an error is raised if no initial x0 is provided to LinearizedADMM solver """ - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Both x0 or "): # Both None _ = LinearizedADMM( proxf=L2(), @@ -93,7 +93,7 @@ def test_LinearizedADMM_noinitial(): x0=None, z0=None, ) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="x0 must be provided when"): # x0 is None (and Op provided) _ = LinearizedADMM( proxf=L2(), @@ -109,7 +109,7 @@ def test_LinearizedADMM_noinitial(): @pytest.mark.parametrize("par", [(par1), (par2)]) def test_GPG_weights(par): """Check GPG raises error if weight is not summing to 1""" - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="must be an array of size"): np.random.seed(0) n, m = par["n"], par["m"] From 25dcf3d62f0a8c3fbb63a988c671e120f1a004d6 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 22 Feb 2026 21:57:46 +0000 Subject: [PATCH 54/72] minor: fix mypy --- pyproximal/proximal/L2.py | 4 ++-- pyproximal/proximal/TV.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproximal/proximal/L2.py b/pyproximal/proximal/L2.py index 76d474e..c018da6 100644 --- a/pyproximal/proximal/L2.py +++ b/pyproximal/proximal/L2.py @@ -359,9 +359,9 @@ def __init__( self.dir = -1 else: self.dir = ( - len(self.dims) - 1 + len(dims) - 1 if dir is None - else get_normalize_axis_index()(dir, len(self.dims)) + else get_normalize_axis_index()(dir, len(dims)) ) # convert data and filter to Fourier domain diff --git a/pyproximal/proximal/TV.py b/pyproximal/proximal/TV.py index 913f28a..2fd6035 100644 --- a/pyproximal/proximal/TV.py +++ b/pyproximal/proximal/TV.py @@ -48,7 +48,7 @@ def __init__( **kwargs: Any, ) -> None: super().__init__(None, True) - self.dims = dims + self.dims = (0,) if dims is None else dims self.ndim = 1 if dims is None else len(dims) self.sigma = sigma self.niter = niter From 3c40919bc27c87537154b1ca6f8319620c690a6d Mon Sep 17 00:00:00 2001 From: Toru Tamaki Date: Tue, 24 Feb 2026 10:47:47 +0900 Subject: [PATCH 55/72] change behavior of DouglasRachfordSplitting - swap x (inverted model) and y (second inverted model) --- pyproximal/optimization/primal.py | 34 ++++++++++++++++++++----------- pytests/test_solver.py | 8 ++++---- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index f7a568a..86ef8f3 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -1734,23 +1734,33 @@ def DouglasRachfordSplitting( Notes ----- The Douglas-Rachford Splitting algorithm can be expressed by the following - recursion [1]_, [2]_: + recursion [1]_, [2]_, [3]_, [4]_: .. math:: - \mathbf{y}^{k} &= \prox_{\tau g}(\mathbf{x}^k) \\ - \mathbf{x}^{k+1} &= \mathbf{x}^{k} + - \eta (\prox_{\tau f}(2 \mathbf{y}^{k} - \mathbf{x}^{k}) - - \mathbf{y}^{k}) + \mathbf{x}^{k} &= \prox_{\tau g}(\mathbf{y}^k) \\ + \mathbf{y}^{k+1} &= \mathbf{y}^{k} + + \eta (\prox_{\tau f}(2 \mathbf{x}^{k} - \mathbf{y}^{k}) + - \mathbf{x}^{k}) .. [1] Patrick L. Combettes and Jean-Christophe Pesquet. 2011. Proximal Splitting Methods in Signal Processing. In Fixed-Point Algorithms for - Inverse Problems in Science and Engineering, Springer, 185-212. + Inverse Problems in Science and Engineering, Springer, pp. 185-212. + Algorithm 10.15. https://doi.org/10.1007/978-1-4419-9569-8_10 .. [2] Scott B. Lindstrom and Brailey Sims. 2021. Survey: Sixty Years of Douglas-Rachford. Journal of the Australian Mathematical Society, 110, - 3, 333-370. https://doi.org/10.1017/S1446788719000570 + 3, 333-370. Eq.(15). https://doi.org/10.1017/S1446788719000570 https://arxiv.org/abs/1809.07181 + .. [3] Ryu, E.K., Yin, W., 2022. Large-Scale Convex Optimization: Algorithms + & Analyses via Monotone Operators. Cambridge University Press, + Cambridge. Eq.(2.18). https://doi.org/10.1017/9781009160865 + https://large-scale-book.mathopt.com/ + .. [4] Combettes, P.L., Pesquet, J.-C., 2008. A proximal decomposition + method for solving convex variational inverse problems. Inverse Problems + 24, 065014. Proposition 3.2. https://doi.org/10.1088/0266-5611/24/6/065014 + https://arxiv.org/abs/0807.2617 + """ if show: @@ -1765,15 +1775,15 @@ def DouglasRachfordSplitting( head = " Itn x[0] f g J = f + g" print(head) - x = x0.copy() + y = x0.copy() for iiter in range(niter): if gfirst: - y = proxg.prox(x, tau) - x = x + eta * (proxf.prox(2 * y - x, tau) - y) + x = proxg.prox(y, tau) + y = y + eta * (proxf.prox(2 * x - y, tau) - x) else: - y = proxf.prox(x, tau) - x = x + eta * (proxg.prox(2 * y - x, tau) - y) + x = proxf.prox(y, tau) + y = y + eta * (proxg.prox(2 * x - y, tau) - x) # run callback if callback is not None: diff --git a/pytests/test_solver.py b/pytests/test_solver.py index 2c532eb..1aeafaa 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -186,7 +186,7 @@ def test_PG_GPG(par): @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ADMM_DRS(par): +def test_ADMM_DRS(par: Dict[str, Any]) -> None: """Check equivalency of ADMM and DouglasRachfordSplitting when using a single regularization term """ @@ -210,19 +210,19 @@ def test_ADMM_DRS(par): # ADMM l2 = L2(Op=Rop, b=y, niter=10, warm=True) l1 = L1(sigma=5e-1) - xadmm = ADMM(l2, l1, x0=np.zeros(m), tau=tau, niter=100, show=True) + xadmm, zadmm = ADMM(l2, l1, x0=np.zeros(m), tau=tau, niter=100, show=True) # DRS with g first l2 = L2(Op=Rop, b=y, niter=10, warm=True) l1 = L1(sigma=5e-1) - xdrs_g = DouglasRachfordSplitting( + xdrs_g, ydrs_g = DouglasRachfordSplitting( l2, l1, x0=np.zeros(m), tau=tau, niter=100, show=True, gfirst=True ) # DRS with f first l2 = L2(Op=Rop, b=y, niter=10, warm=True) l1 = L1(sigma=5e-1) - xdrs_f = DouglasRachfordSplitting( + xdrs_f, ydrs_f = DouglasRachfordSplitting( l2, l1, x0=np.zeros(m), tau=tau, niter=100, show=True, gfirst=False ) From 8d67ee47181291e8d206e07a6667d39071376169 Mon Sep 17 00:00:00 2001 From: Toru Tamaki Date: Thu, 26 Feb 2026 08:34:07 +0900 Subject: [PATCH 56/72] bug fix of ConsensusADMM - fix initialization of y_1, ..., y_m to have a correct dimension --- pyproximal/optimization/primal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index f7a568a..414189f 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -2063,7 +2063,7 @@ def ConsensusADMM( # pylint: disable=invalid-name m = len(proxfs) x_bar = x0.copy() x_bar_old = x0.copy() - y = ncp.zeros_like(x0) + y = ncp.zeros((m, x0.shape[0])) # iterate for iiter in range(niter): From 4fae288779bbf633887922021c5b292c737cfb0d Mon Sep 17 00:00:00 2001 From: Toru Tamaki Date: Fri, 27 Feb 2026 16:29:50 +0900 Subject: [PATCH 57/72] use x0.size instead of x0.shape[0] --- pyproximal/optimization/primal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index 414189f..f7170fa 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -2063,7 +2063,7 @@ def ConsensusADMM( # pylint: disable=invalid-name m = len(proxfs) x_bar = x0.copy() x_bar_old = x0.copy() - y = ncp.zeros((m, x0.shape[0])) + y = ncp.zeros((m, x0.size)) # iterate for iiter in range(niter): From 4a45d17324b7925066366184537f824f892bb420 Mon Sep 17 00:00:00 2001 From: Toru Tamaki Date: Fri, 27 Feb 2026 16:37:15 +0900 Subject: [PATCH 58/72] revert type hint of test_ADMM_DRS --- pyproximal/optimization/primal.py | 1 - pytests/test_solver.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index 86ef8f3..5678eed 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -1761,7 +1761,6 @@ def DouglasRachfordSplitting( 24, 065014. Proposition 3.2. https://doi.org/10.1088/0266-5611/24/6/065014 https://arxiv.org/abs/0807.2617 - """ if show: tstart = time.time() diff --git a/pytests/test_solver.py b/pytests/test_solver.py index 1aeafaa..5ea27a2 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -186,7 +186,7 @@ def test_PG_GPG(par): @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ADMM_DRS(par: Dict[str, Any]) -> None: +def test_ADMM_DRS(par): """Check equivalency of ADMM and DouglasRachfordSplitting when using a single regularization term """ From 469fd0de11d8cee89b064d6fa0bf22d2cdd90ded Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 28 Feb 2026 16:12:20 +0000 Subject: [PATCH 59/72] minor: fixed equation in rMS --- .gitignore | 1 + pyproximal/optimization/primaldual.py | 4 ++-- pyproximal/optimization/segmentation.py | 2 +- pyproximal/optimization/sr3.py | 4 ++-- pyproximal/projection/Box.py | 12 ++++++------ pyproximal/projection/Euclidean.py | 2 +- pyproximal/projection/Intersection.py | 2 +- pyproximal/projection/Simplex.py | 2 +- pyproximal/proximal/Box.py | 4 ++-- pyproximal/proximal/Euclidean.py | 2 +- pyproximal/proximal/Intersection.py | 2 +- pyproximal/proximal/L0.py | 2 +- pyproximal/proximal/L1.py | 4 ++-- pyproximal/proximal/L2.py | 4 ++-- pyproximal/proximal/Nonlinear.py | 2 +- pyproximal/proximal/Orthogonal.py | 2 +- pyproximal/proximal/Quadratic.py | 8 ++++---- pyproximal/proximal/RelaxedMS.py | 10 +++++----- pyproximal/proximal/Sum.py | 4 ++-- pyproximal/proximal/_Simplex_cuda.py | 10 +++++----- pyproximal/proximal/_Simplex_numba.py | 20 ++++++++++---------- pyproximal/utils/moreau.py | 2 +- 22 files changed, 53 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 81732a5..a0f8e75 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ pyproximal.egg-info/ pyproximal/version.py # Development # +.codacy .vscode .ipynb_checkpoints/ notebooks diff --git a/pyproximal/optimization/primaldual.py b/pyproximal/optimization/primaldual.py index 28c1637..d98491b 100644 --- a/pyproximal/optimization/primaldual.py +++ b/pyproximal/optimization/primaldual.py @@ -64,10 +64,10 @@ def PrimalDual( Linear operator of g x0 : :obj:`numpy.ndarray` Initial vector - tau : :obj:`float` or :obj:`np.ndarray` + tau : :obj:`float` or :obj:`numpy.ndarray` Stepsize of subgradient of :math:`f`. This can be constant or function of iterations (in the latter cases provided as np.ndarray) - mu : :obj:`float` or :obj:`np.ndarray` + mu : :obj:`float` or :obj:`numpy.ndarray` Stepsize of subgradient of :math:`g^*`. This can be constant or function of iterations (in the latter cases provided as np.ndarray) y0 : :obj:`numpy.ndarray` diff --git a/pyproximal/optimization/segmentation.py b/pyproximal/optimization/segmentation.py index 51bf084..5063220 100644 --- a/pyproximal/optimization/segmentation.py +++ b/pyproximal/optimization/segmentation.py @@ -29,7 +29,7 @@ def Segment( Parameters ---------- - y : :obj:`np.ndarray` + y : :obj:`numpy.ndarray` Image to segment (must have 2 or more dimensions) cl : :obj:`numpy.ndarray` Classes diff --git a/pyproximal/optimization/sr3.py b/pyproximal/optimization/sr3.py index cf97bfd..10f3381 100644 --- a/pyproximal/optimization/sr3.py +++ b/pyproximal/optimization/sr3.py @@ -41,7 +41,7 @@ def _lsqr( Data iter_lim: :obj:`int` Maximum number of iterations - z_old: :obj:`np.ndarray` + z_old: :obj:`numpy.ndarray` The previous outer iteration x0: :obj:`numpy.ndarray` initial guess @@ -49,7 +49,7 @@ def _lsqr( The regularization parameter for the inner iteration eps: :obj:`float` The regularization parameter for the outer iteration - Reg: :obj:`np.ndarray` + Reg: :obj:`numpy.ndarray` The regularization operator L Returns diff --git a/pyproximal/projection/Box.py b/pyproximal/projection/Box.py index 38e0f01..5b57cff 100644 --- a/pyproximal/projection/Box.py +++ b/pyproximal/projection/Box.py @@ -8,9 +8,9 @@ class BoxProj: Parameters ---------- - lower : :obj:`float` or :obj:`np.ndarray`, optional + lower : :obj:`float` or :obj:`numpy.ndarray`, optional Lower bound - upper : :obj:`float` or :obj:`np.ndarray`, optional + upper : :obj:`float` or :obj:`numpy.ndarray`, optional Upper bound Notes @@ -56,13 +56,13 @@ class HyperPlaneBoxProj: Parameters ---------- - coeffs : :obj:`np.ndarray` + coeffs : :obj:`numpy.ndarray` Vector of coefficients used in the definition of the hyperplane scalar : :obj:`float` Scalar used in the definition of the hyperplane - lower : :obj:`float` or :obj:`np.ndarray`, optional + lower : :obj:`float` or :obj:`numpy.ndarray`, optional Lower bound of Box - upper : :obj:`float` or :obj:`np.ndarray`, optional + upper : :obj:`float` or :obj:`numpy.ndarray`, optional Upper bound of Box maxiter : :obj:`int`, optional Maximum number of iterations used by :func:`scipy.optimize.bisect` @@ -123,7 +123,7 @@ def __call__(self, x: NDArray) -> NDArray: Parameters ---------- - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Vector """ diff --git a/pyproximal/projection/Euclidean.py b/pyproximal/projection/Euclidean.py index 001a9b7..83c99af 100644 --- a/pyproximal/projection/Euclidean.py +++ b/pyproximal/projection/Euclidean.py @@ -7,7 +7,7 @@ class EuclideanBallProj: Parameters ---------- - center : :obj:`np.ndarray` or :obj:`float` + center : :obj:`numpy.ndarray` or :obj:`float` Center of the ball radius : :obj:`float` Radius diff --git a/pyproximal/projection/Intersection.py b/pyproximal/projection/Intersection.py index 30160d3..bdd765d 100644 --- a/pyproximal/projection/Intersection.py +++ b/pyproximal/projection/Intersection.py @@ -11,7 +11,7 @@ class IntersectionProj: Size of vector to be projected n : :obj:`int` Number of vectors to be projected simultaneously - sigma : :obj:`np.ndarray` + sigma : :obj:`numpy.ndarray` Matrix of distances of size :math:`k \times k` niter : :obj:`int`, optional Number of iterations diff --git a/pyproximal/projection/Simplex.py b/pyproximal/projection/Simplex.py index 161dc79..bd21f04 100644 --- a/pyproximal/projection/Simplex.py +++ b/pyproximal/projection/Simplex.py @@ -45,7 +45,7 @@ def __call__(self, x: NDArray) -> NDArray: Parameters ---------- - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Vector maxiter : :obj:`int`, optional Maximum number of iterations used by :func:`scipy.optimize.bisect` diff --git a/pyproximal/proximal/Box.py b/pyproximal/proximal/Box.py index ae56271..7ab0141 100644 --- a/pyproximal/proximal/Box.py +++ b/pyproximal/proximal/Box.py @@ -12,9 +12,9 @@ class Box(ProxOperator): Parameters ---------- - lower : :obj:`float` or :obj:`np.ndarray`, optional + lower : :obj:`float` or :obj:`numpy.ndarray`, optional Lower bound - upper : :obj:`float` or :obj:`np.ndarray`, optional + upper : :obj:`float` or :obj:`numpy.ndarray`, optional Upper bound Notes diff --git a/pyproximal/proximal/Euclidean.py b/pyproximal/proximal/Euclidean.py index c068601..4e1a939 100644 --- a/pyproximal/proximal/Euclidean.py +++ b/pyproximal/proximal/Euclidean.py @@ -70,7 +70,7 @@ class EuclideanBall(ProxOperator): Parameters ---------- - center : :obj:`np.ndarray` or :obj:`float` + center : :obj:`numpy.ndarray` or :obj:`float` Center of the ball radius : :obj:`float` Radius diff --git a/pyproximal/proximal/Intersection.py b/pyproximal/proximal/Intersection.py index 4bf74a1..ab2ea66 100644 --- a/pyproximal/proximal/Intersection.py +++ b/pyproximal/proximal/Intersection.py @@ -14,7 +14,7 @@ class Intersection(ProxOperator): Size of vector to be projected n : :obj:`int` Number of vectors to be projected simultaneously - sigma : :obj:`np.ndarray` or :obj:`float` + sigma : :obj:`numpy.ndarray` or :obj:`float` Matrix of distances of size :math:`k \times k` (or single value in the case of constant matrix) niter : :obj:`int`, optional diff --git a/pyproximal/proximal/L0.py b/pyproximal/proximal/L0.py index 00612a5..95668e8 100644 --- a/pyproximal/proximal/L0.py +++ b/pyproximal/proximal/L0.py @@ -58,7 +58,7 @@ class L0(ProxOperator): Parameters ---------- - sigma : :obj:`float` or :obj:`np.ndarray` or :obj:`func`, optional + sigma : :obj:`float` or :obj:`numpy.ndarray` or :obj:`func`, optional Multiplicative coefficient of L0 norm. This can be a constant number, a list of values (for multidimensional inputs, acting on the second dimension) or a function that is called passing a counter which keeps track of how many diff --git a/pyproximal/proximal/L1.py b/pyproximal/proximal/L1.py index 5af3bfa..42129ae 100644 --- a/pyproximal/proximal/L1.py +++ b/pyproximal/proximal/L1.py @@ -60,13 +60,13 @@ class L1(ProxOperator): Parameters ---------- - sigma : :obj:`float` or :obj:`np.ndarray` or :obj:`func`, optional + sigma : :obj:`float` or :obj:`numpy.ndarray` or :obj:`func`, optional Multiplicative coefficient of L1 norm. This can be a constant number, a list of values (for 2-dimensional inputs, acting on the second dimension) or a function that is called passing a counter which keeps track of how many times the ``prox`` method has been invoked before and returns a scalar (or a list of) ``sigma`` to be used. - g : :obj:`np.ndarray`, optional + g : :obj:`numpy.ndarray`, optional Vector to be subtracted Notes diff --git a/pyproximal/proximal/L2.py b/pyproximal/proximal/L2.py index c018da6..f38f53b 100644 --- a/pyproximal/proximal/L2.py +++ b/pyproximal/proximal/L2.py @@ -48,7 +48,7 @@ class L2(ProxOperator): This can be a constant number or a function that is called passing a counter which keeps track of how many times the ``prox`` method has been invoked before and returns the ``niter`` to be used. - x0 : :obj:`np.ndarray`, optional + x0 : :obj:`numpy.ndarray`, optional Initial vector warm : :obj:`bool`, optional Warm start (``True``) or not (``False``). Uses estimate from previous @@ -316,7 +316,7 @@ class L2Convolve(ProxOperator): Parameters ---------- - h : :obj:`np.ndarray` + h : :obj:`numpy.ndarray` Kernel of convolution operator b : :obj:`numpy.ndarray` Data vector diff --git a/pyproximal/proximal/Nonlinear.py b/pyproximal/proximal/Nonlinear.py index eef13cd..291a51c 100644 --- a/pyproximal/proximal/Nonlinear.py +++ b/pyproximal/proximal/Nonlinear.py @@ -29,7 +29,7 @@ class Nonlinear(ABC, ProxOperator): Parameters ---------- - x0 : :obj:`np.ndarray` + x0 : :obj:`numpy.ndarray` Initial vector niter : :obj:`int`, optional Number of iterations of iterative scheme used to compute the proximal diff --git a/pyproximal/proximal/Orthogonal.py b/pyproximal/proximal/Orthogonal.py index aa9bfa0..b284d5c 100644 --- a/pyproximal/proximal/Orthogonal.py +++ b/pyproximal/proximal/Orthogonal.py @@ -25,7 +25,7 @@ class Orthogonal(ProxOperator): Orthogonal operator partial : :obj:`bool`, optional Partial (``True``) of full (``False``) orthogonality - b : :obj:`np.ndarray`, optional + b : :obj:`numpy.ndarray`, optional Vector alpha : :obj:`float`, optional Positive coefficient for partial orthogonality. It will be ignored if diff --git a/pyproximal/proximal/Quadratic.py b/pyproximal/proximal/Quadratic.py index b4ef387..07ce21d 100644 --- a/pyproximal/proximal/Quadratic.py +++ b/pyproximal/proximal/Quadratic.py @@ -22,13 +22,13 @@ class Quadratic(ProxOperator): ---------- Op : :obj:`pylops.LinearOperator`, optional Linear operator (must be square) - b : :obj:`np.ndarray`, optional + b : :obj:`numpy.ndarray`, optional Vector c : :obj:`float`, optional Scalar niter : :obj:`int`, optional Number of iterations of iterative scheme used to compute the proximal - x0 : :obj:`np.ndarray`, optional + x0 : :obj:`numpy.ndarray`, optional Initial vector warm : :obj:`bool`, optional Warm start (``True``) or not (``False``). Uses estimate from previous @@ -121,12 +121,12 @@ def grad(self, x: NDArray) -> NDArray: Parameters ---------- - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Vector Returns ------- - g : :obj:`np.ndarray` + g : :obj:`numpy.ndarray` Gradient vector """ diff --git a/pyproximal/proximal/RelaxedMS.py b/pyproximal/proximal/RelaxedMS.py index e6047c7..a679a52 100644 --- a/pyproximal/proximal/RelaxedMS.py +++ b/pyproximal/proximal/RelaxedMS.py @@ -40,13 +40,13 @@ class RelaxedMumfordShah(ProxOperator): Parameters ---------- - sigma : :obj:`float` or :obj:`np.ndarray` or :obj:`func`, optional + sigma : :obj:`float` or :obj:`numpy.ndarray` or :obj:`func`, optional Multiplicative coefficient of L2 norm that controls the smoothness of the solutuon. This can be a constant number, a list of values (for multidimensional inputs, acting on the second dimension) or a function that is called passing a counter which keeps track of how many times the ``prox`` method has been invoked before and returns a scalar (or a list of) ``sigma`` to be used. - kappa : :obj:`float` or :obj:`np.ndarray` or :obj:`func`, optional + kappa : :obj:`float` or :obj:`numpy.ndarray` or :obj:`func`, optional Constant value in the rMS norm which essentially controls when the norm allows a jump. This can be a constant number, a list of values (for multidimensional inputs, acting on the second dimension) or a function that is called passing a counter which keeps track of how many @@ -58,10 +58,10 @@ class RelaxedMumfordShah(ProxOperator): The :math:`rMS` proximal operator is defined as [1]_: .. math:: - \text{prox}_{\tau \text{rMS}}(x) = + \text{prox}_{\tau \text{rMS}}(\mathbf{x}) = \begin{cases} - \frac{1}{1+2\tau\alpha}x & \text{ if } & \vert x\vert \leq \sqrt{\frac{\kappa}{\alpha}(1 + 2\tau\alpha)} \\ - \kappa & \text{ else } + \frac{1}{1+2\tau\alpha}x_i & \text{ if } & \vert x_i\vert \leq \sqrt{\frac{\kappa}{\alpha}(1 + 2\tau\alpha)} \\ + x_i & \text{ else } \end{cases}. .. [1] Strekalovskiy, E., and D. Cremers, 2014, Real-time minimization of the piecewise smooth diff --git a/pyproximal/proximal/Sum.py b/pyproximal/proximal/Sum.py index e20e13d..b386050 100644 --- a/pyproximal/proximal/Sum.py +++ b/pyproximal/proximal/Sum.py @@ -20,7 +20,7 @@ class Sum(ProxOperator): ---------- ops : :obj:`list` A list of proximable functions :math:`f_1, \ldots, f_m`. - weights : :obj:`np.ndarray` or :obj:`list` or :obj:`None`, optional, default=None + weights : :obj:`numpy.ndarray` or :obj:`list` or :obj:`None`, optional, default=None Weights :math:`\sum_{i=1}^m w_i = 1, \ 0 < w_i < 1`, used when :math:`m > 2`, or :math:`m = 2` and ``use_parallel=True``. Defaults to None, which means :math:`w_1 = \cdots = w_m = \frac{1}{m}.` @@ -142,7 +142,7 @@ def __call__(self, x: NDArray) -> bool | float: Parameters ---------- - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Vector Returns diff --git a/pyproximal/proximal/_Simplex_cuda.py b/pyproximal/proximal/_Simplex_cuda.py index 75862fd..87ec39c 100644 --- a/pyproximal/proximal/_Simplex_cuda.py +++ b/pyproximal/proximal/_Simplex_cuda.py @@ -39,15 +39,15 @@ def simplex_jit_cuda(x, coeffs, scalar, lower, upper, maxiter, ftol, xtol, y): Parameters ---------- - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Input vector - coeffs : :obj:`np.ndarray` + coeffs : :obj:`numpy.ndarray` Vector of coefficients used in the definition of the hyperplane scalar : :obj:`float` Scalar used in the definition of the hyperplane - lower : :obj:`float` or :obj:`np.ndarray`, optional + lower : :obj:`float` or :obj:`numpy.ndarray`, optional Lower bound of Box - upper : :obj:`float` or :obj:`np.ndarray`, optional + upper : :obj:`float` or :obj:`numpy.ndarray`, optional Upper bound of Box maxiter : :obj:`int`, optional Maximum number of iterations @@ -55,7 +55,7 @@ def simplex_jit_cuda(x, coeffs, scalar, lower, upper, maxiter, ftol, xtol, y): Function tolerance xtol : :obj:`float`, optional Solution absolute tolerance - y : :obj:`np.ndarray` + y : :obj:`numpy.ndarray` Output vector """ diff --git a/pyproximal/proximal/_Simplex_numba.py b/pyproximal/proximal/_Simplex_numba.py index f04537b..177e8e3 100644 --- a/pyproximal/proximal/_Simplex_numba.py +++ b/pyproximal/proximal/_Simplex_numba.py @@ -37,19 +37,19 @@ def bisect_jit( Parameters ---------- - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Input vector - coeffs : :obj:`np.ndarray` + coeffs : :obj:`numpy.ndarray` Vector of coefficients used in the definition of the hyperplane scalar : :obj:`float` Scalar used in the definition of the hyperplane - lower : :obj:`float` or :obj:`np.ndarray`, optional + lower : :obj:`float` or :obj:`numpy.ndarray`, optional Lower bound of Box - upper : :obj:`float` or :obj:`np.ndarray`, optional + upper : :obj:`float` or :obj:`numpy.ndarray`, optional Upper bound of Box - bisect_lower : :obj:`float` or :obj:`np.ndarray`, optional + bisect_lower : :obj:`float` or :obj:`numpy.ndarray`, optional Lower end of bisection - bisect_upper : :obj:`float` or :obj:`np.ndarray`, optional + bisect_upper : :obj:`float` or :obj:`numpy.ndarray`, optional Upper end of bisection maxiter : :obj:`int`, optional Maximum number of iterations @@ -91,15 +91,15 @@ def simplex_jit( Parameters ---------- - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Input vector - coeffs : :obj:`np.ndarray` + coeffs : :obj:`numpy.ndarray` Vector of coefficients used in the definition of the hyperplane scalar : :obj:`float` Scalar used in the definition of the hyperplane - lower : :obj:`float` or :obj:`np.ndarray`, optional + lower : :obj:`float` or :obj:`numpy.ndarray`, optional Lower bound of Box - upper : :obj:`float` or :obj:`np.ndarray`, optional + upper : :obj:`float` or :obj:`numpy.ndarray`, optional Upper bound of Box maxiter : :obj:`int`, optional Maximum number of iterations diff --git a/pyproximal/utils/moreau.py b/pyproximal/utils/moreau.py index c5f684f..e9374a2 100644 --- a/pyproximal/utils/moreau.py +++ b/pyproximal/utils/moreau.py @@ -25,7 +25,7 @@ def moreau( ---------- prox : :obj:`pyproximal.ProxOperator` Proximal operator - x : :obj:`np.ndarray` + x : :obj:`numpy.ndarray` Vector tau : :obj:`float` Positive scalar weight From 37455c73baddf135fc5940a6247915ae8a8eb7db Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 7 Mar 2026 20:01:33 +0000 Subject: [PATCH 60/72] build: switch to hatchling --- .gitignore | 2 +- pyproject.toml | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index a0f8e75..4b851c1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ build dist pyproximal.egg-info/ -# setuptools_scm generated # +# hatchling generated # pyproximal/version.py # Development # diff --git a/pyproject.toml b/pyproject.toml index fc6022b..672b972 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,9 @@ [build-system] requires = [ - "setuptools >= 65", - "setuptools_scm[toml]", - "wheel", + "hatchling", + "hatch-vcs" ] -build-backend = "setuptools.build_meta" +build-backend = "hatchling.build" [project] name = "pyproximal" @@ -70,11 +69,18 @@ doc = [ "image", ] -[tool.setuptools.packages.find] -exclude = ["pytests"] +[project.urls] +Homepage = "https://github.com/PyLops/pyproximal" +Documentation = "https://pyproximal.readthedocs.io/en/stable" +Discussions = "https://github.com/PyLops/pyproximal/discussions" +Issues = "https://github.com/PyLops/pyproximal/issues" -[tool.setuptools_scm] -version_file = "pyproximal/version.py" +[tool.hatch.build.targets.wheel] +packages = ["pyproximal"] + +[tool.hatch] +version.source = "vcs" +build.hooks.vcs.version-file = "pyproximal/version.py" [tool.pytest.ini_options] addopts = "--verbose" From 3d2f8f28abf958f6b20c1a2ac901fd4afdefcd7d Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 7 Mar 2026 20:18:02 +0000 Subject: [PATCH 61/72] bug: fixes https://github.com/PyLops/pyproximal/issues/240 --- pyproximal/optimization/primal.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index 378b620..428e7c4 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -888,7 +888,9 @@ def GeneralizedProximalGradient( ) msg = "%6g %12.5e %10.3e %10.3e %10.3e" % ( iiter + 1, - x[0] if x.ndim == 1 else x[0, 0], + np.real(to_numpy(x[0])) + if x.ndim == 1 + else np.real(to_numpy(x[0, 0])), pf, pg, pf + pg, From 57fab32f2f077a922f4d801ee2c6246c958087a0 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 7 Mar 2026 20:25:50 +0000 Subject: [PATCH 62/72] minor: reorder hatch in pyproject.toml --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 672b972..3ca3bbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,13 +75,13 @@ Documentation = "https://pyproximal.readthedocs.io/en/stable" Discussions = "https://github.com/PyLops/pyproximal/discussions" Issues = "https://github.com/PyLops/pyproximal/issues" -[tool.hatch.build.targets.wheel] -packages = ["pyproximal"] - [tool.hatch] version.source = "vcs" build.hooks.vcs.version-file = "pyproximal/version.py" +[tool.hatch.build.targets.wheel] +packages = ["pyproximal"] + [tool.pytest.ini_options] addopts = "--verbose" python_files = ["pytests/*.py"] From fef0fe957340c2853fb029d8ba941dc60e4c3e54 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 7 Mar 2026 20:38:47 +0000 Subject: [PATCH 63/72] minor: added exclude for sdist --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3ca3bbd..cd2ee8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,9 @@ build.hooks.vcs.version-file = "pyproximal/version.py" [tool.hatch.build.targets.wheel] packages = ["pyproximal"] +[tool.hatch.build.targets.sdist] +exclude = ["/.github", "/testdata"] + [tool.pytest.ini_options] addopts = "--verbose" python_files = ["pytests/*.py"] From f8981aa62a656ce8f6dcfc99c3dc1d206697aaff Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 8 Mar 2026 15:31:53 +0000 Subject: [PATCH 64/72] Remove SCM step in Github Actions --- .github/workflows/build.yaml | 7 ++----- .github/workflows/codacy-coverage-reporter.yaml | 13 ++----------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4dd6174..4d0082a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,11 +17,8 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 - - name: Get history and tags for SCM versioning to work - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Check out source repository + uses: actions/checkout@v4 - name: Install uv with Python ${{ matrix.python-version }} uses: astral-sh/setup-uv@v6 with: diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index 579c725..14931ef 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -17,33 +17,24 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 - - - name: Get history and tags for SCM versioning to work - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - + - name: Check out source repository + uses: actions/checkout@v4 - name: Install uv with Python ${{ matrix.python-version }} uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies and pyproximal run: uv sync --locked --all-extras --all-groups - - name: Coverage with pytest run: | uv run coverage run -m pytest uv run coverage xml uv run coverage html - - name: Upload HTML coverage report uses: actions/upload-artifact@v4 with: name: coverage-html path: htmlcov/ - - name: Run codacy-coverage-reporter if: github.event_name == 'push' uses: codacy/codacy-coverage-reporter-action@v1 From e3920b6104850b5e64839790471bc2fd592ab7b2 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 8 Mar 2026 15:46:31 +0000 Subject: [PATCH 65/72] minor: better alignment between GAs --- .github/workflows/build.yaml | 5 ++++- .github/workflows/codacy-coverage-reporter.yaml | 16 ++++++---------- .github/workflows/mypy.yaml | 4 +++- .github/workflows/ruff.yaml | 6 ++++-- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4d0082a..5702c85 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,9 +16,12 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ${{ matrix.platform }} + steps: - name: Check out source repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Install uv with Python ${{ matrix.python-version }} uses: astral-sh/setup-uv@v6 with: diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index 14931ef..856a946 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -9,20 +9,16 @@ on: jobs: build: - strategy: - matrix: - platform: [ubuntu-latest] - python-version: ["3.11"] - - runs-on: ${{ matrix.platform }} - + runs-on: ubuntu-latest steps: - name: Check out source repository - uses: actions/checkout@v4 - - name: Install uv with Python ${{ matrix.python-version }} + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Install uv with Python uses: astral-sh/setup-uv@v6 with: - python-version: ${{ matrix.python-version }} + python-version: "3.11" - name: Install dependencies and pyproximal run: uv sync --locked --all-extras --all-groups - name: Coverage with pytest diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 08fbd56..1c40c80 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -9,7 +9,9 @@ jobs: name: MyPy steps: - name: Check out source repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Install uv with Python uses: astral-sh/setup-uv@v6 with: diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index c4ba642..3ab4a5c 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -10,8 +10,10 @@ jobs: name: Lint steps: - name: Check out source repository - uses: actions/checkout@v4 - - name: ruff Lint + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Lint with ruff uses: astral-sh/ruff-action@v3 with: src: "./pyproximal" From 8211b6704a4ed364a95aedc1f5e967a4815c889a Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 8 Mar 2026 16:12:28 +0000 Subject: [PATCH 66/72] ci: updated deploy Github action --- .github/workflows/deploy.yaml | 73 +++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index fb48608..01e3a23 100755 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,28 +1,67 @@ # This workflow uploads PyProx on PyPI using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries +# For more information see: https://github.com/python-attrs/attrs/blob/main/.github/workflows/pypi-package.yml name: PyProximal-deploy on: + push: + tags: ["*"] release: - types: [published] + types: + - published + workflow_dispatch: jobs: - deploy: + build-package: + name: Build & verify package runs-on: ubuntu-latest + + steps: + - name: Check out source repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Build and inspect package + uses: hynek/build-and-inspect-python-package@v2 + + release-test-pypi: + name: Publish in-dev package to test.pypi.org + if: github.repository_owner == 'PyLops' && github.ref_type == 'tag' + runs-on: ubuntu-latest + needs: build-package + permissions: + contents: read + id-token: write + + steps: + - name: Download package + uses: actions/download-artifact@v8 + with: + name: Packages + path: dist + + - name: Upload package to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + attestations: true + repository-url: https://test.pypi.org/legacy/ + + release-pypi: + name: Publish released package to pypi.org + if: github.repository_owner == 'PyLops' && github.event.action == 'published' + runs-on: ubuntu-latest + needs: build-package + permissions: + contents: read + id-token: write + steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - name: Download package + uses: actions/download-artifact@v8 with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + name: Packages + path: dist + + - name: Upload package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + attestations: true From 8621dd11b77b72c96e5eda67a8acbd5a5ac6623f Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 8 Mar 2026 19:03:36 +0000 Subject: [PATCH 67/72] minor: remove setuptools_scm from environment files --- environment-dev-arm.yml | 1 - environment-dev.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/environment-dev-arm.yml b/environment-dev-arm.yml index d386d10..845d36b 100644 --- a/environment-dev-arm.yml +++ b/environment-dev-arm.yml @@ -22,7 +22,6 @@ dependencies: - bm4d - bm3d - pytest-runner - - setuptools_scm - pooch - shibuya - sphinx-design diff --git a/environment-dev.yml b/environment-dev.yml index e39a571..b5f8651 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -23,7 +23,6 @@ dependencies: - bm4d - bm3d - pytest-runner - - setuptools_scm - pooch - shibuya - sphinx-design From 21b2e472bae4bda38d8917089ca94fa91deabdd8 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sun, 8 Mar 2026 22:32:48 +0000 Subject: [PATCH 68/72] minor: small improvements to installation doc --- docs/source/installation.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 3af93dc..fa3dc52 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -5,7 +5,6 @@ Dependencies ************ - The mandatory dependencies of PyProximal are limited to: * Python 3.10 or greater @@ -54,10 +53,9 @@ First install `pyproximal` with your package manager of choice. >> uv add "pyproximal[advanced]" - From Source =========== -To access the latest source from github: +To access the latest source from GitHub: .. tab-set:: @@ -89,7 +87,6 @@ Fork the `PyProximal repository `_ and clo Install dependencies ==================== - We recommend installing dependencies into a separate environment. For that end, we provide a `Makefile` with useful commands for setting up the environment. @@ -178,7 +175,7 @@ At this point, the user must check the changes and then stage them before trying Final steps =========== -PyLops does not enforce the use of a linter as a pre-commit hook, but we do highly encourage using one before submitting a Pull Request. +PyProximal does not enforce the use of a linter as a pre-commit hook, but we do highly encourage using one before submitting a Pull Request. A properly configured linter (``ruff``) can be run with: .. tab-set:: From 175a94d5e536cac7feb47f4e61bb0244ce9c9bc1 Mon Sep 17 00:00:00 2001 From: Toru Tamaki Date: Wed, 11 Mar 2026 23:30:56 +0900 Subject: [PATCH 69/72] add dtype to initialization of y in ConsensusADMM --- pyproximal/optimization/primal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index f7170fa..8f01767 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -2063,7 +2063,7 @@ def ConsensusADMM( # pylint: disable=invalid-name m = len(proxfs) x_bar = x0.copy() x_bar_old = x0.copy() - y = ncp.zeros((m, x0.size)) + y = ncp.zeros((m, x0.size), dtype=x0.dtype) # iterate for iiter in range(niter): From c7efdecd2637f52a16eca45e575eacbfba14c92c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Thu, 12 Mar 2026 20:06:17 +0000 Subject: [PATCH 70/72] minor: small improvements in doc about installation --- README.md | 32 ++++++++++++++++++-------------- docs/source/contributing.rst | 2 +- docs/source/installation.rst | 15 ++++++++------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 231b558..76750b5 100644 --- a/README.md +++ b/README.md @@ -107,33 +107,37 @@ This repository is organized as follows: ## Getting started You need **Python 3.10 or greater**. -#### From Conda +#### From PyPI +To get the most out of PyProximal straight out of the box, we recommend using +the PyPI distribution via `uv`: -To get the most out of PyLops straight out of the box, we recommend `conda` to install PyLops: ```bash -conda install -c conda-forge pyproximal +uv pip install pyproximal ``` -#### From PyPI -You can also install pyproximal with `pip`: +or directly via `pip`: + ```bash pip install pyproximal ``` -or via `uv`: + +#### From Conda +You can also install PyProximal via `conda`: + ```bash -uv pip install pyproximal +conda install -c conda-forge pyproximal ``` #### From Github -Finally, you can also directly install from the main branch (although this is not recommended): +Finally, you can also directly install from the main branch (although this is not recommended) via `uv`: -``` -pip install git+https://git@github.com/PyLops/pyproximal.git@main -``` -or via `uv`: ```bash uv add git+https://github.com/PyLops/pyproximal.git --branch main ``` +or via `pip`: +```bash +pip install git+https://git@github.com/PyLops/pyproximal.git@main +``` ## Contributing *Feel like contributing to the project? Adding new operators or tutorial?* @@ -150,8 +154,8 @@ Execute the following command in your terminal: git clone https://github.com/your_name_here/pyproximal.git ``` -### 2. Install PyLops in a new Conda environment -To ensure that further development of PyLops is performed within the same environment (i.e., +### 2. Install PyProximal in a new Conda environment +To ensure that further development of PyProximal is performed within the same environment (i.e., same dependencies) as that defined by ``environment-dev.yml``/``environment-dev-arm.yml`` files, we suggest to work off a new Conda enviroment. diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 2272ec7..127bfac 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -113,7 +113,7 @@ will also be run as part of our CI. .. tab-set:: - .. tab-item:: conda + .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: bash diff --git a/docs/source/installation.rst b/docs/source/installation.rst index fa3dc52..a16d6d9 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -37,8 +37,8 @@ First install `pyproximal` with your package manager of choice. >> conda install --channel conda-forge pyproximal - Most of the dependencies (all required and some of the optional) are - automatically installed for you. + which installs also the *required* dependencies, if not already present + in your environment. .. tab-item:: :iconify:`material-icon-theme:uv` uv @@ -46,8 +46,8 @@ First install `pyproximal` with your package manager of choice. >> uv add pyproximal - Only the *required* dependencies are installed. To install - some of the optional dependencies, run: + which installs also the *required* dependencies, if not already present + in your environment. To also install the optional dependencies, run: .. code-block:: bash @@ -79,7 +79,8 @@ Step-by-step installation for developers Fork PyProximal =============== -Fork the `PyProximal repository `_ and clone it by executing the following in your terminal: +Fork the `PyProximal repository `_ and clone it +by executing the following in your terminal: .. code-block:: bash @@ -175,8 +176,8 @@ At this point, the user must check the changes and then stage them before trying Final steps =========== -PyProximal does not enforce the use of a linter as a pre-commit hook, but we do highly encourage using one before submitting a Pull Request. -A properly configured linter (``ruff``) can be run with: +PyProximal does enforce the use of a linter (``ruff``), which is run both as a pre-commit hook and as a GitHub Action. +The linter can also be run locally with: .. tab-set:: From 5f5ca4fb54eaf55e72d79600d13e422d57f0b151 Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 4 Apr 2026 20:20:34 +0100 Subject: [PATCH 71/72] minor: small improvements to Makefile --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b319e76..443f731 100755 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ UV := $(shell command -v uv 2> /dev/null || command which uv 2> /dev/null) NOX := $(shell command -v nox 2> /dev/null || command which nox 2> /dev/null) .PHONY: install_conda dev-install_conda dev-install_conda_arm dev-install_uv -.PHONY: tests tests_uv tests_nox doc doc_uv docupdate docupdate_uv servedoc +.PHONY: tests tests_uv tests_nox doc doc_uv docupdate docupdate_uv servedoc servedoc_uv .PHONY: lint lint_uv typeannot typeannot_uv coverage coverage_uv pipcheck: @@ -57,7 +57,7 @@ tests_nox: $(NOX) -s tests doc: - cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\ + cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\ rm -rf source/tutorials && rm -rf source/examples &&\ rm -rf build && make html && cd .. @@ -68,6 +68,7 @@ doc_uv: rm -rf build && $(UV) run make html && cd .. docupdate: + make pythoncheck cd docs && make html && cd .. docupdate_uv: From 94ad9a90f72410db4c33a867c61e87446be46a8c Mon Sep 17 00:00:00 2001 From: mrava87 Date: Sat, 18 Apr 2026 20:38:26 +0100 Subject: [PATCH 72/72] minor: prepare for v0.12.0 --- CHANGELOG.md | 9 +++++++++ docs/source/changelog.rst | 12 ++++++++++++ environment-dev-arm.yml | 2 +- environment-dev.yml | 2 +- environment.yml | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e90f32..3a4d705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Changelog ========= +# 0.12.0 + +* Added ``pyproximal.optimization.primal.ConsensusADMM`` + and ``pyproximal.optimization.primal.PPXA`` solvers +* Modified role of x and y in + ``pyproximal.optimization.primal.DouglasRachfordSplitting`` +* Fix norm computation in ``pyproximal.proximal.L1`` for + 2D-arrays + # 0.11.1 * Added ``typing_extensions`` to dependencies diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index d6719f0..6c31058 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -3,6 +3,18 @@ |:newspaper_roll:| Changelog ############################ +Version 0.12.1 +-------------- +*Released on: 18/04/2026* + +* Added :py:class:`pyproximal.optimization.primal.ConsensusADMM` + and :py:class:`pyproximal.optimization.primal.PPXA` solvers +* Modified role of x and y in + :py:class:`pyproximal.optimization.primal.DouglasRachfordSplitting` +* Fix norm computation in :py:class:`pyproximal.proximal.L1` for + 2D-arrays + + Version 0.11.1 -------------- *Released on: 25/11/2025* diff --git a/environment-dev-arm.yml b/environment-dev-arm.yml index d386d10..c241fcb 100644 --- a/environment-dev-arm.yml +++ b/environment-dev-arm.yml @@ -7,7 +7,7 @@ dependencies: - python>=3.11.0 - numpy>=1.21.0 - scipy>=1.14.0 - - pylops>=2.0.0 + - pylops>=2.4.0 - scikit-image - matplotlib - typing_extensions>=4.0.0 diff --git a/environment-dev.yml b/environment-dev.yml index e39a571..63264eb 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -7,7 +7,7 @@ dependencies: - python>=3.11.0 - numpy>=1.21.0 - scipy>=1.14.0 - - pylops>=2.0.0 + - pylops>=2.4.0 - scikit-image - matplotlib - typing_extensions>=4.0.0 diff --git a/environment.yml b/environment.yml index 1a5ca30..0c893af 100644 --- a/environment.yml +++ b/environment.yml @@ -5,5 +5,5 @@ dependencies: - python>=3.11.0 - numpy>=1.21.0 - scipy>=1.14.0 - - pylops>=2.0.0 + - pylops>=2.4.0 - typing_extensions>=4.0.0 diff --git a/pyproject.toml b/pyproject.toml index fc6022b..77037b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ requires-python = ">=3.10" dependencies = [ "numpy >= 1.21.0", "scipy >= 1.11.0", - "pylops >= 2.0.0", + "pylops >= 2.4.0", "typing_extensions >= 4.0.0", ] dynamic = ["version"] diff --git a/uv.lock b/uv.lock index 513612f..15a7fed 100644 --- a/uv.lock +++ b/uv.lock @@ -1928,7 +1928,7 @@ requires-dist = [ { name = "llvmlite", marker = "extra == 'advanced'" }, { name = "numba", marker = "extra == 'advanced'" }, { name = "numpy", specifier = ">=1.21.0" }, - { name = "pylops", specifier = ">=2.0.0" }, + { name = "pylops", specifier = ">=2.4.0" }, { name = "scipy", specifier = ">=1.11.0" }, { name = "typing-extensions", specifier = ">=4.0.0" }, ]