mirror of
https://github.com/tgorordo/carousel.git
synced 2026-06-12 20:42:13 -07:00
init cgi script and html form
This commit is contained in:
parent
740ae98027
commit
5da4a21a3f
9 changed files with 909 additions and 110 deletions
88
README.md
88
README.md
|
|
@ -14,36 +14,30 @@ bibliography: REFERENCES.bib
|
|||
|
||||
### Gale-Shapley Deferred Acceptance
|
||||
|
||||
The most basic versions of the stable matching problem was outlined and solved by [@gale&shapley1962].
|
||||
The most basic version(s) of the stable matching problem was outlined and solved in [@gale&shapley1962] and won Shapley the
|
||||
[2012 Nobel Prize in Economics](https://www.nobelprize.org/prizes/economic-sciences/2012/popular-information/).
|
||||
|
||||
The basic problem being solved is as follows:
|
||||
|
||||
#### Stable Marriage Problem
|
||||
The "Stable Marriage problem" TODO
|
||||
|
||||
TODO
|
||||
|
||||
## Usage
|
||||
Using `carousel` is pretty simple once it's set up: given some input rankings, and some post-selection criteria
|
||||
the program should generate a landscape of valid matching solutions for you to choose from (and can generate more on request).
|
||||
|
||||
### Installation, Setup, Dependencies & Tooling
|
||||
There are a number of ways to guarantee you have the required dependencies to run `carousel`.
|
||||
The most complete method is using `uv` (with `nix` and `direnv`), but a plain/more barebones setup using `venv` is also possible.
|
||||
|
||||
#### Setup and run with `uv`
|
||||
`carousel` was developed using the the [`uv`](https://github.com/astral-sh/uv) package and project manager.
|
||||
#### College Admissions Problem
|
||||
Solving the stable marriage problem also provides a solution to the "College Admissions Problem",
|
||||
with just a little more work.
|
||||
|
||||
TODO
|
||||
|
||||
#### Raw setup with `venv`
|
||||
It's possible to only use only default Python tooling, if so desired, via the
|
||||
[`venv` module](https://docs.python.org/3/library/venv.html).
|
||||
## Data - Input/Output
|
||||
All [input table formats supported by `polars`](https://docs.pola.rs/user-guide/io/) are supported by `carousel` (`csv`, `excel`, `json` to name a few),
|
||||
which accepts a few inter-related tabular schemes for the input/output data.
|
||||
|
||||
TODO
|
||||
|
||||
#### Convenience [`direnv`](https://github.com/direnv/direnv) and [`nix`](https://github.com/NixOS/nix) environment management and [`just`](https://github.com/casey/just) taskrunner.
|
||||
TODO
|
||||
|
||||
### Matching: Input & Output
|
||||
|
||||
All [input table formats supported by `polars`](https://docs.pola.rs/user-guide/io/) are supported by `carousel`.
|
||||
Input data should be in one of three forms:
|
||||
### Input
|
||||
Input describes the preferences/rankings of the "applicants" of "reviewers" to which they will be matched (possibly many-to-one, as in the "College Admission Problem"),
|
||||
as well as the preferences/rankings for the reviwers of applicants.
|
||||
Input should be in one of three forms:
|
||||
|
||||
#### Preferences
|
||||
Preferences enumerate by-name some preferences in descending order,
|
||||
|
|
@ -78,6 +72,8 @@ a pair of corresponding rankings, *or* a matrix encoding both rankings at once:
|
|||
| CaseMed | (3, 2) | (1, 1) | (2, 3) |
|
||||
| Emory | (2, 1) | (3, 3) | (1, 2) |
|
||||
|
||||
### Output
|
||||
|
||||
#### Matching
|
||||
A matching is a table whose rows list the applicants matched to each reviewer
|
||||
e.g. a matching from the med-school ranking matrix in the previous section might look like
|
||||
|
|
@ -99,10 +95,52 @@ TODO check/make stable.
|
|||
|
||||
TODO matching more people per school e.g.
|
||||
|
||||
### Matching: Post-Selection
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
There are 4 main ways to use Carousel:
|
||||
|
||||
### UO Pages Server - CGI Form
|
||||
An HTML form submission interface is hosted at
|
||||
|
||||
> [`https://pages.uoregon.edu/tgorordo/forms/carousel.html`](https://pages.uoregon.edu/tgorordo/forms/carousel.html)
|
||||
|
||||
using the [pages.uoregon.edu CGI feature](https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069).
|
||||
|
||||
Submit your applicant and reviewer preferences in tabular form,
|
||||
or as excel uploads and the server will return a table of matches for you to choose from.
|
||||
|
||||
### Command Line Binary
|
||||
|
||||
TODO
|
||||
|
||||
### GUI Binary
|
||||
|
||||
TODO
|
||||
|
||||
### Python Library/Development
|
||||
If you prefer to invoke `carousel` directly (or incorporate it as a library into another script)
|
||||
in a python environment instead of using any of the bundled/released versions of the program described above (or wish to
|
||||
reproduce those bundles), you can do so using the [`uv` environment/package/project manager](https://github.com/astral-sh/uv)
|
||||
or a raw python virtual environment using the [`venv` module](https://docs.python.org/3/library/venv.html)
|
||||
(if you need an intro to python `venv`s see [this page](https://pages.uoregon.edu/tgorordo/uoph410-510a_Image-Analysis/venvs.html)).
|
||||
|
||||
Some extra command-line development conveniences are available if you use the tools:
|
||||
|
||||
- [`just`](https://github.com/casey/just) is a taskrunner that can execute the provided `justfile` of some common useful commands.
|
||||
- [`direnv`](https://github.com/direnv/direnv) with [`nix` (shell)](https://github.com/NixOS/nix) can guarantee minimal development tooling without polluting your broader environment. i.e. they can auto-install and run all of carousel's tooling in an environment specific to your development directory.
|
||||
|
||||
but everything provided by these tools can also be done using more standard/default shell tooling.
|
||||
[`uv`](https://github.com/astral-sh/uv) as your package/environment manager is highly recommended, however.
|
||||
|
||||
TODO
|
||||
|
||||
## Post-Selection
|
||||
It's often desirable to enforce additional criteria on solutions
|
||||
that are not well-posed within the core optimization problem.
|
||||
Since the solver itself is stochastic, these are often most easily implemented
|
||||
Since the solver itself is stochastic to some extent, these are often most easily implemented
|
||||
by a post-selection.
|
||||
|
||||
|
||||
|
|
|
|||
5
justfile
5
justfile
|
|
@ -1,11 +1,14 @@
|
|||
run:
|
||||
uv run carousel
|
||||
|
||||
python *arguments:
|
||||
uv run python -c "import code; from rich import pretty; pretty.install(); code.interact()" {{arguments}}
|
||||
|
||||
check:
|
||||
uv run pyright src
|
||||
|
||||
test:
|
||||
uv run pytest -vv --tb=short
|
||||
uv run pytest -vvv --tb=short --log-cli-level=INFO
|
||||
|
||||
format:
|
||||
uv run ruff format src test
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ dependencies = [
|
|||
"numpy>=2.2.4",
|
||||
"polars>=1.26.0",
|
||||
"pyside6>=6.9.0",
|
||||
"pytest-benchmark>=5.1.0",
|
||||
"rich>=14.0.0",
|
||||
]
|
||||
|
||||
|
|
@ -28,11 +27,17 @@ dev = [
|
|||
"pyinstaller>=6.12.0",
|
||||
"pyright>=1.1.398",
|
||||
"pytest>=8.3.5",
|
||||
"pytest-benchmark>=5.1.0",
|
||||
"ruff>=0.11.2",
|
||||
]
|
||||
srv = [
|
||||
"legacy-cgi>=2.6.3",
|
||||
"python-dotenv>=1.1.0",
|
||||
]
|
||||
|
||||
[pytest]
|
||||
testpaths = "test"
|
||||
log_cli = true
|
||||
|
||||
[tool.pyright]
|
||||
include = ["src"]
|
||||
|
|
|
|||
|
|
@ -1,18 +1,28 @@
|
|||
import logging, rich
|
||||
from rich.logging import RichHandler
|
||||
|
||||
rich.traceback.install()
|
||||
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
import polars as pl
|
||||
import polars.selectors as pls
|
||||
|
||||
logging.basicConfig(
|
||||
level="NOTSET",
|
||||
level=logging.INFO,
|
||||
format="%(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True, tracebacks_suppress=[pl, pls])],
|
||||
handlers=[
|
||||
RichHandler(
|
||||
show_time=True,
|
||||
markup=True,
|
||||
rich_tracebacks=True,
|
||||
tracebacks_suppress=[pl, pls, np],
|
||||
)
|
||||
],
|
||||
)
|
||||
log = logging.getLogger("rich")
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def rank_to_pref(ranking):
|
||||
|
|
@ -64,6 +74,7 @@ def ranking_matrix(A, B):
|
|||
|
||||
|
||||
def check_valid_pref(preferences):
|
||||
"""A valid set of preferences has all unique entries in each column, TODO"""
|
||||
repeats = preferences.select(
|
||||
(~pl.all_horizontal((pl.all().is_unique() | pl.all().is_null()).all())).alias(
|
||||
"repeats"
|
||||
|
|
@ -73,6 +84,7 @@ def check_valid_pref(preferences):
|
|||
|
||||
|
||||
def check_valid_rank(ranking):
|
||||
"""A valid ranking has no ties, TODO"""
|
||||
ties = ranking.select(
|
||||
(~pl.all_horizontal((pl.all().is_unique() | pl.all().is_null()).all())).alias(
|
||||
"ties"
|
||||
|
|
@ -91,6 +103,16 @@ def check_valid_assgn(assgn, applicants, reviewers):
|
|||
pass
|
||||
|
||||
|
||||
def assgn_to_match(assgn):
|
||||
# TODO
|
||||
pass
|
||||
|
||||
|
||||
def match_to_assgn(match):
|
||||
# TODO
|
||||
pass
|
||||
|
||||
|
||||
def get_rank(ranking, ranker, ranked):
|
||||
idx = ranking.select(pl.arg_where(pl.col("") == ranked)).item()
|
||||
return ranking[ranker][idx]
|
||||
|
|
@ -122,22 +144,56 @@ def check_stable(*args, **kwargs):
|
|||
return not check_unstable(*args, **kwargs)
|
||||
|
||||
|
||||
def deferred_acceptance(applicant_ranking, reviewer_ranking):
|
||||
def deferred_acceptance(applicant_rankings, reviewer_rankings):
|
||||
"""Find the Gale-Shapley deferred-acceptance stable matching for preferences A, R."""
|
||||
applicants = applicant_ranking.columns[1:]
|
||||
reviewers = reviewer_ranking.columns[1:]
|
||||
# TODO - the core algorithm!
|
||||
pass
|
||||
reviewer_rankings = reviewer_rankings.rename(
|
||||
{reviewer_rankings.columns[0]: "applicant"}
|
||||
)
|
||||
|
||||
app_prefs = rank_to_pref(applicant_rankings)
|
||||
offers = app_prefs.transpose(
|
||||
include_header=True,
|
||||
header_name="applicant",
|
||||
column_names=["pref" + str(i + 1) for i in range(app_prefs.width)],
|
||||
).with_columns(pl.coalesce(pl.all().exclude("applicant")).alias("offer"))
|
||||
|
||||
def assgn_to_match(assgn):
|
||||
# TODO
|
||||
pass
|
||||
# offers = pl.concat(pl.align_frames(offers, reviewer_rankings, on="applicant"), how="horizontal")
|
||||
offers = pl.concat([offers, reviewer_rankings], how="align_left")
|
||||
|
||||
match = pl.DataFrame(
|
||||
{
|
||||
r: offers.select(pl.col("applicant", "offer").sort_by(r))
|
||||
.select(
|
||||
pl.when(pl.col("offer").eq(r)).then(pl.col("applicant")).otherwise(None)
|
||||
)
|
||||
.select(pl.all().fill_null(strategy="backward").first())
|
||||
.to_series()
|
||||
for r in reviewer_rankings.columns[1:]
|
||||
}
|
||||
) # .select(pl.all().fill_null(strategy="backward").first())
|
||||
|
||||
def match_to_assgn(match):
|
||||
# TODO
|
||||
pass
|
||||
# while check_unstable(match, applicant_rankings, reviewer_rankings):
|
||||
while match.select(pl.any_horizontal(pl.all().has_nulls())).item():
|
||||
# TODO null applicant preferences that rejected
|
||||
|
||||
rejected_applicants = offers.select(
|
||||
pl.col("applicant").is_in(match.row(0)).alias("matched")
|
||||
)
|
||||
|
||||
return match
|
||||
|
||||
offers = offers.with_columns(pl.col("pref"))
|
||||
|
||||
offers = offers.with_columns(
|
||||
pl.coalesce(
|
||||
# TODO: select prefn columns using a regex
|
||||
).alias("offer")
|
||||
)
|
||||
|
||||
# TODO update match
|
||||
|
||||
# else if stable
|
||||
return match
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
|
|
|||
24
src/cgi/carousel.py
Normal file
24
src/cgi/carousel.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
logdir = os.getenv("CAROUSEL_LOGDIR")
|
||||
|
||||
import cgi, cgitb
|
||||
|
||||
cgitb.enable(display=0, logdir=logdir)
|
||||
|
||||
form = cgi.FieldStorage()
|
||||
|
||||
from ..carousel import *
|
||||
|
||||
print("Content-Type: text/html")
|
||||
print()
|
||||
|
||||
print("<title>Carousel Matches!</title>")
|
||||
print("<h1>Carousel Stable Matches Found:</h1>")
|
||||
|
||||
print("TODO - INPUTH ECHO NOT IMPLEMENTED")
|
||||
print("TODO - OUTPUT NOT IMPLEMENTED")
|
||||
1
src/cgi/default.env
Normal file
1
src/cgi/default.env
Normal file
|
|
@ -0,0 +1 @@
|
|||
CAROUSEL_LOGDIR="$HOME/logs/carousel/"
|
||||
652
src/cgi/form.html
Normal file
652
src/cgi/form.html
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dynamic Data Entry Form</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Georgia, serif;
|
||||
line-height: 1.6;
|
||||
color: #1a1a1a;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 15px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.controls-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.fill-default-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.fill-default-btn {
|
||||
background: #777;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.fill-default-btn:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.disabled-table {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #555;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background 0.3s;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
button.secondary:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
min-width: 600px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
th input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
td input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
.file-upload {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.file-upload label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
text-align: center;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background 0.3s;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
.submit-button:hover {
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 5px;
|
||||
background-color: #f1f1f1;
|
||||
margin-top: 10px;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 100%;
|
||||
background-color: #666;
|
||||
width: 0%;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: #f1f5f1;
|
||||
border: 1px solid #ddd;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #f5f1f1;
|
||||
border: 1px solid #ddd;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
border-bottom: none;
|
||||
border-radius: 3px 3px 0 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #fff;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
border-top: 1px solid #ddd;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
border-left-color: #555;
|
||||
animation: spin 1s linear infinite;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
(<a href="https://pages.uoregon.edu/tgorordo">Back to ~/tgorordo</a>)
|
||||
<div class="container">
|
||||
<h1>Carousel Stable Matcher</h1>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" id="table-tab">Table Input</div>
|
||||
<div class="tab" id="file-tab">File Upload</div>
|
||||
</div>
|
||||
|
||||
<form id="data-form" action="/cgi-bin/carousel.py" method="post" enctype="multipart/form-data">
|
||||
<div class="tab-content active" id="table-content">
|
||||
<!-- First Table Section -->
|
||||
<div class="form-section">
|
||||
<div class="form-header">
|
||||
<h2>Applicant Preferences</h2>
|
||||
<div class="controls">
|
||||
<button type="button" id="add-column-1">Add Column</button>
|
||||
<button type="button" id="add-row-1">Add Row</button>
|
||||
<button type="button" class="secondary" id="remove-column-1">Remove Column</button>
|
||||
<button type="button" class="secondary" id="remove-row-1">Remove Row</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table id="data-table-1">
|
||||
<thead>
|
||||
<tr id="header-row-1">
|
||||
<th><input type="text" placeholder="Applicant 1" name="table1_header_0" required></th>
|
||||
<th><input type="text" placeholder="Applicant 2" name="table1_header_1" required></th>
|
||||
<th><input type="text" placeholder="Applicant 3" name="table1_header_2" required></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text" placeholder="Preference 1..." name="table1_row_0_col_0"></td>
|
||||
<td><input type="text" placeholder="Preference 1..." name="table1_row_0_col_1"></td>
|
||||
<td><input type="text" placeholder="Preference 1..." name="table1_row_0_col_2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="text" name="table1_row_1_col_0"></td>
|
||||
<td><input type="text" name="table1_row_1_col_1"></td>
|
||||
<td><input type="text" name="table1_row_1_col_2"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Second Table Section -->
|
||||
<div class="form-section">
|
||||
<div class="form-header">
|
||||
<h2>Reviewer Preferences</h2>
|
||||
<div class="controls-container">
|
||||
<div class="controls">
|
||||
<button type="button" id="add-column-2">Add Column</button>
|
||||
<button type="button" id="add-row-2">Add Row</button>
|
||||
<button type="button" class="secondary" id="remove-column-2">Remove Column</button>
|
||||
<button type="button" class="secondary" id="remove-row-2">Remove Row</button>
|
||||
</div>
|
||||
<div class="fill-default-control">
|
||||
<label for="fill-default">
|
||||
<button type="button" id="fill-default-btn" class="fill-default-btn">Fill Default</button>
|
||||
<input type="checkbox" id="fill-default" name="fill_default"> (Use default uniform preferences.)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container" id="table2-container">
|
||||
<table id="data-table-2">
|
||||
<thead>
|
||||
<tr id="header-row-2">
|
||||
<th><input type="text" placeholder="Reviewer 1" name="table2_header_0" required></th>
|
||||
<th><input type="text" placeholder="Reviewer 2" name="table2_header_1" required></th>
|
||||
<th><input type="text" placeholder="Reviewer 3" name="table2_header_2" required></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text" placeholder="Preference 1..." name="table2_row_0_col_0"></td>
|
||||
<td><input type="text" placeholder="Preference 1..." name="table2_row_0_col_1"></td>
|
||||
<td><input type="text" placeholder="Preference 1..." name="table2_row_0_col_2"></td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="text" name="table2_row_1_col_0"></td>
|
||||
<td><input type="text" name="table2_row_1_col_1"></td>
|
||||
<td><input type="text" name="table2_row_1_col_2"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="file-content">
|
||||
<div class="form-section">
|
||||
<div class="file-upload">
|
||||
<label for="file-input">Upload a CSV or Excel file:</label>
|
||||
<input type="file" id="file-input" name="file" accept=".csv,.xlsx,.xls">
|
||||
<div class="file-info" id="file-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section submit-section">
|
||||
<button type="submit" class="submit-button">
|
||||
<span id="spinner" class="spinner"></span>
|
||||
<span id="submit-text">Submit Preferences</span>
|
||||
</button>
|
||||
<div class="progress-bar" id="progress-bar">
|
||||
<div class="progress" id="progress"></div>
|
||||
</div>
|
||||
<div class="message" id="message"></div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<details>
|
||||
<summary><b>Input/Output Format Documentation</b></summary>
|
||||
<p>
|
||||
The matcher assumes a specific tabular scheme for the input data - to read more about how this works see the
|
||||
<a href="https://github.com/tgorordo/carousel">README</a>.
|
||||
|
||||
You can enter preference information directly in the tables above, or upload an Excel spreadsheet with
|
||||
two sheets of preferences, or a CSV containing a ranking matrix.
|
||||
</p> More on each format scheme:
|
||||
|
||||
|
||||
|
||||
<h3>Tabular Input</h3>
|
||||
You can enter tables of preferences directly in the first tab of the form above.
|
||||
|
||||
<h4>Example:</h4>
|
||||
|
||||
<h3>File Upload</h3>
|
||||
|
||||
For more complicated input, a file upload might be preferable to table form submission
|
||||
(e.g. if there's a validation issue you won't have to manually re-enter the table, just tweak and re-upload the file).
|
||||
|
||||
Two file formats are supported, Excel and CSV:
|
||||
|
||||
<h4>Excel</h4>
|
||||
|
||||
<h4>CSV</h4>
|
||||
|
||||
</details>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Tab switching functionality
|
||||
document.getElementById('table-tab').addEventListener('click', () => {
|
||||
switchTab('table');
|
||||
});
|
||||
|
||||
document.getElementById('file-tab').addEventListener('click', () => {
|
||||
switchTab('file');
|
||||
});
|
||||
|
||||
function switchTab(tabName) {
|
||||
// Hide all tabs and contents
|
||||
document.querySelectorAll('.tab').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
// Show the selected tab and content
|
||||
document.getElementById(`${tabName}-tab`).classList.add('active');
|
||||
document.getElementById(`${tabName}-content`).classList.add('active');
|
||||
}
|
||||
|
||||
// Table manipulation functions
|
||||
function setupTableControls(tableNumber) {
|
||||
const addColumnBtn = document.getElementById(`add-column-${tableNumber}`);
|
||||
const addRowBtn = document.getElementById(`add-row-${tableNumber}`);
|
||||
const removeColumnBtn = document.getElementById(`remove-column-${tableNumber}`);
|
||||
const removeRowBtn = document.getElementById(`remove-row-${tableNumber}`);
|
||||
const dataTable = document.getElementById(`data-table-${tableNumber}`);
|
||||
const headerRow = document.getElementById(`header-row-${tableNumber}`);
|
||||
|
||||
addColumnBtn.addEventListener('click', () => {
|
||||
const colCount = headerRow.cells.length;
|
||||
|
||||
// Add header cell
|
||||
const newHeader = document.createElement('th');
|
||||
newHeader.innerHTML = `<input type="text" placeholder="Column ${colCount + 1}" name="table${tableNumber}_header_${colCount}" required>`;
|
||||
headerRow.appendChild(newHeader);
|
||||
|
||||
// Add cell to each row
|
||||
const rows = dataTable.querySelectorAll('tbody tr');
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const newCell = document.createElement('td');
|
||||
newCell.innerHTML = `<input type="text" name="table${tableNumber}_row_${rowIndex}_col_${colCount}">`;
|
||||
row.appendChild(newCell);
|
||||
});
|
||||
});
|
||||
|
||||
addRowBtn.addEventListener('click', () => {
|
||||
const tbody = dataTable.querySelector('tbody');
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
const rowCount = rows.length;
|
||||
const colCount = headerRow.cells.length;
|
||||
|
||||
// Create new row
|
||||
const newRow = document.createElement('tr');
|
||||
|
||||
// Add cells to the new row
|
||||
for (let i = 0; i < colCount; i++) {
|
||||
const newCell = document.createElement('td');
|
||||
newCell.innerHTML = `<input type="text" name="table${tableNumber}_row_${rowCount}_col_${i}">`;
|
||||
newRow.appendChild(newCell);
|
||||
}
|
||||
|
||||
tbody.appendChild(newRow);
|
||||
});
|
||||
|
||||
removeColumnBtn.addEventListener('click', () => {
|
||||
const colCount = headerRow.cells.length;
|
||||
|
||||
if (colCount <= 1) {
|
||||
alert('Table must have at least one column.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the last header cell
|
||||
headerRow.deleteCell(colCount - 1);
|
||||
|
||||
// Remove the last cell from each row
|
||||
const rows = dataTable.querySelectorAll('tbody tr');
|
||||
rows.forEach(row => {
|
||||
row.deleteCell(colCount - 1);
|
||||
});
|
||||
});
|
||||
|
||||
removeRowBtn.addEventListener('click', () => {
|
||||
const tbody = dataTable.querySelector('tbody');
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
|
||||
if (rows.length <= 1) {
|
||||
alert('Table must have at least one row.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the last row
|
||||
tbody.removeChild(rows[rows.length - 1]);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup controls for both tables
|
||||
setupTableControls(1);
|
||||
setupTableControls(2);
|
||||
|
||||
// Fill Default functionality for Table 2
|
||||
const fillDefaultCheckbox = document.getElementById('fill-default');
|
||||
const fillDefaultBtn = document.getElementById('fill-default-btn');
|
||||
const table2Container = document.getElementById('table2-container');
|
||||
|
||||
fillDefaultBtn.addEventListener('click', () => {
|
||||
fillDefaultCheckbox.checked = !fillDefaultCheckbox.checked;
|
||||
toggleTable2State();
|
||||
});
|
||||
|
||||
fillDefaultCheckbox.addEventListener('change', toggleTable2State);
|
||||
|
||||
function toggleTable2State() {
|
||||
if (fillDefaultCheckbox.checked) {
|
||||
table2Container.classList.add('disabled-table');
|
||||
} else {
|
||||
table2Container.classList.remove('disabled-table');
|
||||
}
|
||||
}
|
||||
|
||||
// File upload handling
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const fileInfo = document.getElementById('file-info');
|
||||
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
fileInfo.textContent = `Selected: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`;
|
||||
} else {
|
||||
fileInfo.textContent = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Form submission
|
||||
const dataForm = document.getElementById('data-form');
|
||||
const submitButton = document.querySelector('.submit-button');
|
||||
const spinner = document.getElementById('spinner');
|
||||
const submitText = document.getElementById('submit-text');
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
const progress = document.getElementById('progress');
|
||||
const message = document.getElementById('message');
|
||||
|
||||
dataForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Show spinner and disable button
|
||||
spinner.style.display = 'inline-block';
|
||||
submitButton.disabled = true;
|
||||
submitText.textContent = 'Submitting...';
|
||||
progressBar.style.display = 'block';
|
||||
message.style.display = 'none';
|
||||
message.className = 'message';
|
||||
|
||||
// Collect form data
|
||||
const formData = new FormData(dataForm);
|
||||
|
||||
// Add a flag to indicate which tab is active
|
||||
formData.append('input_type', document.getElementById('table-content').classList.contains('active') ? 'table' : 'file');
|
||||
|
||||
// If table input is active, add table structure info
|
||||
if (document.getElementById('table-content').classList.contains('active')) {
|
||||
// Table 1 info
|
||||
const colCount1 = document.getElementById('header-row-1').cells.length;
|
||||
const rowCount1 = document.getElementById('data-table-1').querySelectorAll('tbody tr').length;
|
||||
formData.append('table1_col_count', colCount1);
|
||||
formData.append('table1_row_count', rowCount1);
|
||||
|
||||
// Table 2 info
|
||||
const colCount2 = document.getElementById('header-row-2').cells.length;
|
||||
const rowCount2 = document.getElementById('data-table-2').querySelectorAll('tbody tr').length;
|
||||
formData.append('table2_col_count', colCount2);
|
||||
formData.append('table2_row_count', rowCount2);
|
||||
formData.append('table2_use_default', document.getElementById('fill-default').checked);
|
||||
}
|
||||
|
||||
// Simulate upload progress (in a real application, you'd track actual upload progress)
|
||||
let width = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
if (width >= 90) {
|
||||
clearInterval(progressInterval);
|
||||
} else {
|
||||
width += 5;
|
||||
progress.style.width = width + '%';
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Send form data to server
|
||||
fetch(dataForm.action, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
clearInterval(progressInterval);
|
||||
progress.style.width = '100%';
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Server error: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Show success message
|
||||
message.textContent = data.message || 'Data submitted successfully!';
|
||||
message.classList.add('success');
|
||||
message.style.display = 'block';
|
||||
})
|
||||
.catch(error => {
|
||||
// Show error message
|
||||
console.error('Error:', error);
|
||||
message.textContent = 'An error occurred while submitting the data. Please try again.';
|
||||
message.classList.add('error');
|
||||
message.style.display = 'block';
|
||||
})
|
||||
.finally(() => {
|
||||
// Hide spinner and re-enable button
|
||||
setTimeout(() => {
|
||||
spinner.style.display = 'none';
|
||||
submitButton.disabled = false;
|
||||
submitText.textContent = 'Submit Data';
|
||||
progressBar.style.display = 'none';
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,27 +1,14 @@
|
|||
import logging, rich
|
||||
from rich.logging import RichHandler
|
||||
|
||||
import polars as pl
|
||||
import polars.selectors as pls
|
||||
import numpy as np
|
||||
|
||||
from polars.testing import assert_frame_equal
|
||||
|
||||
import pytest
|
||||
import pytest, rich
|
||||
from hypothesis import given, strategies as st
|
||||
|
||||
import carousel as crsl
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level="NOTSET",
|
||||
format="%(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True, tracebacks_suppress=[np, pl, pls])],
|
||||
)
|
||||
|
||||
log = logging.getLogger("rich")
|
||||
|
||||
rng = np.random.default_rng()
|
||||
|
||||
|
||||
|
|
@ -57,8 +44,6 @@ def test_invalid_pref():
|
|||
|
||||
|
||||
def test_pref_to_rank():
|
||||
rr = crsl.pref_to_rank(p)
|
||||
rich.print(p, rr, r)
|
||||
assert_frame_equal(crsl.pref_to_rank(p), r, check_dtypes=False)
|
||||
|
||||
|
||||
|
|
@ -139,3 +124,12 @@ def test_eg2_isstable():
|
|||
match = pl.DataFrame({"A": ["c"], "B": ["d"], "C": ["a"], "D": ["b"]})
|
||||
|
||||
assert crsl.check_stable(match, ar, rr)
|
||||
|
||||
|
||||
@given(
|
||||
rankings(names=["a", "b", "c", "d"], choices=["A", "B", "C", "D"]),
|
||||
rankings(names=["A", "B", "C", "D"], choices=["a", "b", "c", "d"]),
|
||||
)
|
||||
def test_defacc_isstable(applicant_rankings, reviewer_rankings):
|
||||
match = crsl.deferred_acceptance(applicant_rankings, reviewer_rankings)
|
||||
assert crsl.check_stable(match, applicant_rankings, reviewer_rankings)
|
||||
|
|
|
|||
132
uv.lock
generated
132
uv.lock
generated
|
|
@ -42,6 +42,10 @@ dev = [
|
|||
{ name = "pytest-benchmark" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
srv = [
|
||||
{ name = "legacy-cgi" },
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
|
|
@ -62,6 +66,10 @@ dev = [
|
|||
{ name = "pytest-benchmark", specifier = ">=5.1.0" },
|
||||
{ name = "ruff", specifier = ">=0.11.2" },
|
||||
]
|
||||
srv = [
|
||||
{ name = "legacy-cgi", specifier = ">=2.6.3" },
|
||||
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
|
|
@ -86,15 +94,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "hypothesis"
|
||||
version = "6.131.5"
|
||||
version = "6.131.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "sortedcontainers" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3c/49/8d61e63b60b8e9584618a8fdbc1401b27480f9c25fdde2d41fe3372e7def/hypothesis-6.131.5.tar.gz", hash = "sha256:e76b192dc4fd033d7c33f94d8775bcbfd522a143b67adca30513e7727ebe7af6", size = 433127 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/ff/217417d065aa8a4e6815ddc39acee1222f1b67bd0e4803b85de86a837873/hypothesis-6.131.9.tar.gz", hash = "sha256:ee9b0e1403e1121c91921dbdc79d7f509fdb96d457a0389222d2a68d6c8a8f8e", size = 435415 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/34/83/f1427ae8f94b9e2327d2487aacd7645f1c01b12e556c92d3d8cca57aa28b/hypothesis-6.131.5-py3-none-any.whl", hash = "sha256:a97bdc0f23276fb4c52b99aeb7888adfbe052722c4cf9e398a228b48cf2c4504", size = 497966 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/e5/41a6733bfe11997795669dec3b3d785c28918e06568a2540dcc29f0d3fa7/hypothesis-6.131.9-py3-none-any.whl", hash = "sha256:7c2d9d6382e98e5337b27bd34e5b223bac23956787a827e1d087e00d893561d6", size = 499853 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -106,6 +114,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "legacy-cgi"
|
||||
version = "2.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/ed/300cabc9693209d5a03e2ebc5eb5c4171b51607c08ed84a2b71c9015e0f3/legacy_cgi-2.6.3.tar.gz", hash = "sha256:4c119d6cb8e9d8b6ad7cc0ddad880552c62df4029622835d06dfd18f438a8154", size = 24401 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/33/68c6c38193684537757e0d50a7ccb4f4656e5c2f7cd2be737a9d4a1bff71/legacy_cgi-2.6.3-py3-none-any.whl", hash = "sha256:6df2ea5ae14c71ef6f097f8b6372b44f6685283dc018535a75c924564183cdab", size = 19851 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macholib"
|
||||
version = "1.16.3"
|
||||
|
|
@ -150,39 +167,39 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.4"
|
||||
version = "2.2.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -287,15 +304,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.399"
|
||||
version = "1.1.400"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nodeenv" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/9d/d91d5f6d26b2db95476fefc772e2b9a16d54c6bd0ea6bb5c1b6d635ab8b4/pyright-1.1.399.tar.gz", hash = "sha256:439035d707a36c3d1b443aec980bc37053fbda88158eded24b8eedcf1c7b7a1b", size = 3856954 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6c/cb/c306618a02d0ee8aed5fb8d0fe0ecfed0dbf075f71468f03a30b5f4e1fe0/pyright-1.1.400.tar.gz", hash = "sha256:b8a3ba40481aa47ba08ffb3228e821d22f7d391f83609211335858bf05686bdb", size = 3846546 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/b5/380380c9e7a534cb1783c70c3e8ac6d1193c599650a55838d0557586796e/pyright-1.1.399-py3-none-any.whl", hash = "sha256:55f9a875ddf23c9698f24208c764465ffdfd38be6265f7faf9a176e1dc549f3b", size = 5592584 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/a5/5d285e4932cf149c90e3c425610c5efaea005475d5f96f1bfdb452956c62/pyright-1.1.400-py3-none-any.whl", hash = "sha256:c80d04f98b5a4358ad3a35e241dbf2a408eee33a40779df365644f8054d2517e", size = 5563460 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -374,6 +391,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/9e/d6/b41653199ea09d5969d4e385df9bbfd9a100f28ca7e824ce7c0a016e3053/pytest_benchmark-5.1.0-py3-none-any.whl", hash = "sha256:922de2dfa3033c227c96da942d1878191afa135a29485fb942e85dff1c592c89", size = 44259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.3"
|
||||
|
|
@ -398,36 +424,36 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/11/bcef6784c7e5d200b8a1f5c2ddf53e5da0efec37e6e5a44d163fb97e04ba/ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79", size = 4010053 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/89/6f9c9674818ac2e9cc2f2b35b704b7768656e6b7c139064fc7ba8fbc99f1/ruff-0.11.7.tar.gz", hash = "sha256:655089ad3224070736dc32844fde783454f8558e71f501cb207485fe4eee23d4", size = 4054861 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/1f/8848b625100ebcc8740c8bac5b5dd8ba97dd4ee210970e98832092c1635b/ruff-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:d84dcbe74cf9356d1bdb4a78cf74fd47c740bf7bdeb7529068f69b08272239a1", size = 10248105 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/47/c44036e70c6cc11e6ee24399c2a1e1f1e99be5152bd7dff0190e4b325b76/ruff-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9bc583628e1096148011a5d51ff3c836f51899e61112e03e5f2b1573a9b726de", size = 11001494 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/5b/170444061650202d84d316e8f112de02d092bff71fafe060d3542f5bc5df/ruff-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2959049faeb5ba5e3b378709e9d1bf0cab06528b306b9dd6ebd2a312127964a", size = 10352151 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/91/f02839fb3787c678e112c8865f2c3e87cfe1744dcc96ff9fc56cfb97dda2/ruff-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c5d4e30d9d0de7fedbfb3e9e20d134b73a30c1e74b596f40f0629d5c28a193", size = 10541951 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f3/c09933306096ff7a08abede3cc2534d6fcf5529ccd26504c16bf363989b5/ruff-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4b9a4e1439f7d0a091c6763a100cef8fbdc10d68593df6f3cfa5abdd9246e", size = 10079195 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/0d/a87f8933fccbc0d8c653cfbf44bedda69c9582ba09210a309c066794e2ee/ruff-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5edf270223dd622218256569636dc3e708c2cb989242262fe378609eccf1308", size = 11698918 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/7d/8eac0bd083ea8a0b55b7e4628428203441ca68cd55e0b67c135a4bc6e309/ruff-0.11.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f55844e818206a9dd31ff27f91385afb538067e2dc0beb05f82c293ab84f7d55", size = 12319426 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/dc/d0c17d875662d0c86fadcf4ca014ab2001f867621b793d5d7eef01b9dcce/ruff-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d8f782286c5ff562e4e00344f954b9320026d8e3fae2ba9e6948443fafd9ffc", size = 11791012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/f3/81a1aea17f1065449a72509fc7ccc3659cf93148b136ff2a8291c4bc3ef1/ruff-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01c63ba219514271cee955cd0adc26a4083df1956d57847978383b0e50ffd7d2", size = 13949947 },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/9f/a3e34de425a668284e7024ee6fd41f452f6fa9d817f1f3495b46e5e3a407/ruff-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15adac20ef2ca296dd3d8e2bedc6202ea6de81c091a74661c3666e5c4c223ff6", size = 11471753 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c5/4a57a86d12542c0f6e2744f262257b2aa5a3783098ec14e40f3e4b3a354a/ruff-0.11.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4dd6b09e98144ad7aec026f5588e493c65057d1b387dd937d7787baa531d9bc2", size = 10417121 },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/3f/a3b4346dff07ef5b862e2ba06d98fcbf71f66f04cf01d375e871382b5e4b/ruff-0.11.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:45b2e1d6c0eed89c248d024ea95074d0e09988d8e7b1dad8d3ab9a67017a5b03", size = 10073829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/cc/7ed02e0b86a649216b845b3ac66ed55d8aa86f5898c5f1691797f408fcb9/ruff-0.11.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd40de4115b2ec4850302f1a1d8067f42e70b4990b68838ccb9ccd9f110c5e8b", size = 11076108 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/5e/5b09840fef0eff1a6fa1dea6296c07d09c17cb6fb94ed5593aa591b50460/ruff-0.11.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77cda2dfbac1ab73aef5e514c4cbfc4ec1fbef4b84a44c736cc26f61b3814cd9", size = 11512366 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/4c/1cd5a84a412d3626335ae69f5f9de2bb554eea0faf46deb1f0cb48534042/ruff-0.11.6-py3-none-win32.whl", hash = "sha256:5151a871554be3036cd6e51d0ec6eef56334d74dfe1702de717a995ee3d5b287", size = 10485900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/46/8997872bc44d43df986491c18d4418f1caff03bc47b7f381261d62c23442/ruff-0.11.6-py3-none-win_amd64.whl", hash = "sha256:cce85721d09c51f3b782c331b0abd07e9d7d5f775840379c640606d3159cae0e", size = 11558592 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/6a/65fecd51a9ca19e1477c3879a7fda24f8904174d1275b419422ac00f6eee/ruff-0.11.6-py3-none-win_arm64.whl", hash = "sha256:3567ba0d07fb170b1b48d944715e3294b77f5b7679e8ba258199a250383ccb79", size = 10682766 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/ec/21927cb906c5614b786d1621dba405e3d44f6e473872e6df5d1a6bca0455/ruff-0.11.7-py3-none-linux_armv6l.whl", hash = "sha256:d29e909d9a8d02f928d72ab7837b5cbc450a5bdf578ab9ebee3263d0a525091c", size = 10245403 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/af/fec85b6c2c725bcb062a354dd7cbc1eed53c33ff3aa665165871c9c16ddf/ruff-0.11.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dd1fb86b168ae349fb01dd497d83537b2c5541fe0626e70c786427dd8363aaee", size = 11007166 },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/9a/2d0d260a58e81f388800343a45898fd8df73c608b8261c370058b675319a/ruff-0.11.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3d7d2e140a6fbbc09033bce65bd7ea29d6a0adeb90b8430262fbacd58c38ada", size = 10378076 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/c4/9b09b45051404d2e7dd6d9dbcbabaa5ab0093f9febcae664876a77b9ad53/ruff-0.11.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4809df77de390a1c2077d9b7945d82f44b95d19ceccf0c287c56e4dc9b91ca64", size = 10557138 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/5e/f62a1b6669870a591ed7db771c332fabb30f83c967f376b05e7c91bccd14/ruff-0.11.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3a0c2e169e6b545f8e2dba185eabbd9db4f08880032e75aa0e285a6d3f48201", size = 10095726 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/59/a7aa8e716f4cbe07c3500a391e58c52caf665bb242bf8be42c62adef649c/ruff-0.11.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49b888200a320dd96a68e86736cf531d6afba03e4f6cf098401406a257fcf3d6", size = 11672265 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/e3/101a8b707481f37aca5f0fcc3e42932fa38b51add87bfbd8e41ab14adb24/ruff-0.11.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2b19cdb9cf7dae00d5ee2e7c013540cdc3b31c4f281f1dacb5a799d610e90db4", size = 12331418 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/71/037f76cbe712f5cbc7b852e4916cd3cf32301a30351818d32ab71580d1c0/ruff-0.11.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64e0ee994c9e326b43539d133a36a455dbaab477bc84fe7bfbd528abe2f05c1e", size = 11794506 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/de/e450b6bab1fc60ef263ef8fcda077fb4977601184877dce1c59109356084/ruff-0.11.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bad82052311479a5865f52c76ecee5d468a58ba44fb23ee15079f17dd4c8fd63", size = 13939084 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/2c/1e364cc92970075d7d04c69c928430b23e43a433f044474f57e425cbed37/ruff-0.11.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7940665e74e7b65d427b82bffc1e46710ec7f30d58b4b2d5016e3f0321436502", size = 11450441 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/7d/1b048eb460517ff9accd78bca0fa6ae61df2b276010538e586f834f5e402/ruff-0.11.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:169027e31c52c0e36c44ae9a9c7db35e505fee0b39f8d9fca7274a6305295a92", size = 10441060 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/57/8dc6ccfd8380e5ca3d13ff7591e8ba46a3b330323515a4996b991b10bd5d/ruff-0.11.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:305b93f9798aee582e91e34437810439acb28b5fc1fee6b8205c78c806845a94", size = 10058689 },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/bf/20487561ed72654147817885559ba2aa705272d8b5dee7654d3ef2dbf912/ruff-0.11.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a681db041ef55550c371f9cd52a3cf17a0da4c75d6bd691092dfc38170ebc4b6", size = 11073703 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/27/04f2db95f4ef73dccedd0c21daf9991cc3b7f29901a4362057b132075aa4/ruff-0.11.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:07f1496ad00a4a139f4de220b0c97da6d4c85e0e4aa9b2624167b7d4d44fd6b6", size = 11532822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/72/43b123e4db52144c8add336581de52185097545981ff6e9e58a21861c250/ruff-0.11.7-py3-none-win32.whl", hash = "sha256:f25dfb853ad217e6e5f1924ae8a5b3f6709051a13e9dad18690de6c8ff299e26", size = 10362436 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/a0/3e58cd76fdee53d5c8ce7a56d84540833f924ccdf2c7d657cb009e604d82/ruff-0.11.7-py3-none-win_amd64.whl", hash = "sha256:0a931d85959ceb77e92aea4bbedfded0a31534ce191252721128f77e5ae1f98a", size = 11566676 },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/ca/69d7c7752bce162d1516e5592b1cc6b6668e9328c0d270609ddbeeadd7cf/ruff-0.11.7-py3-none-win_arm64.whl", hash = "sha256:778c1e5d6f9e91034142dfd06110534ca13220bfaad5c3735f6cb844654f6177", size = 10677936 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "78.1.0"
|
||||
version = "79.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/71/b6365e6325b3290e14957b2c3a804a529968c77a049b2ed40c095f749707/setuptools-79.0.1.tar.gz", hash = "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88", size = 1367909 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/6d/b4752b044bf94cb802d88a888dc7d288baaf77d7910b7dedda74b5ceea0c/setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51", size = 1256281 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue