mirror of
https://github.com/tgorordo/smithy.git
synced 2026-06-05 16:22:15 -07:00
Compare commits
65 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fcdae8792 | ||
|
|
c324dc9c9f | ||
| e0d0134fdd | |||
|
|
fc0569e252 | ||
|
|
51d651bee6 | ||
|
|
bc146d523d | ||
|
|
17c121f0af | ||
|
|
e9c263a8c5 | ||
|
|
fa24eb5758 | ||
|
|
55f64d1a32 | ||
|
|
3dc8a3cb46 | ||
|
|
845ac38533 | ||
|
|
b24920c15a | ||
|
|
24243aad2f | ||
|
|
f762fd5a23 | ||
|
|
57caad7f27 | ||
|
|
1a6de605ad | ||
|
|
d1db77c466 | ||
| 18405644d3 | |||
| 9e1e5d9642 | |||
| be82bc4ba5 | |||
| 368770d792 | |||
|
|
1a668a7cee | ||
|
|
808df7b4d4 | ||
|
|
16ea974654 | ||
|
|
70cda86acb | ||
|
|
144d9cc9a9 | ||
|
|
c5c93f5aa2 | ||
| c12be4d9db | |||
| 0c334f699e | |||
|
|
19fadd84d8 | ||
| 2f07283f26 | |||
| 22765fe052 | |||
| 86e880623f | |||
| 458015d214 | |||
| 674fdc1fe9 | |||
| fcf2505820 | |||
| 62923fefaf | |||
| 3f9f4e6c98 | |||
| 915ba47ccc | |||
|
|
43e060ee03 | ||
|
|
a66b1efeae | ||
|
|
ee89a7bf11 | ||
|
|
a7f265c103 | ||
|
|
f71ec59d01 | ||
| 92b464fccf | |||
|
|
a66116712b | ||
|
|
ea9652be87 | ||
|
|
dd0b306c1e | ||
| 5d0f1da322 | |||
| 8b39991bcd | |||
| 2138a0ea6b | |||
| 3e56cbfa18 | |||
| 7a30e0a85d | |||
| 84d6380081 | |||
| 47de09c59c | |||
| 4cc6f98273 | |||
| 4e437fa570 | |||
| a945d2e8a4 | |||
| 497fecd997 | |||
| b8b7bb5e9c | |||
|
|
5b4a819113 | ||
| 8ab9667978 | |||
| c57068b34c | |||
| 174df7e572 |
20 changed files with 4094 additions and 255 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use nix
|
||||
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
__pycache__/
|
||||
|
||||
.venv
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
smithycmd.spec
|
||||
smithygui.spec
|
||||
build/
|
||||
dist/
|
||||
|
||||
.ipynb_checkpoints
|
||||
.ruff_cache/
|
||||
.pytest_cache/
|
||||
.benchmarks/
|
||||
181
README.md
181
README.md
|
|
@ -1,5 +1,180 @@
|
|||
# Smithy
|
||||
*A simple smith set solver for ranked-choice ballots.*
|
||||
*A simple Smith set solver for ranked-choice ballots.*
|
||||
|
||||
The [Smith set](https://en.wikipedia.org/wiki/Smith_set) is the minimal set of election candidates which can beat all others pairwise
|
||||
(by simple majority ranking preference) - if there is a single winner in the set they are
|
||||
guaranteed the standard [Condorcet i.e. Majority winner](https://en.wikipedia.org/wiki/Condorcet_winner) (they beat all others pairwise).
|
||||
|
||||
`smithy` identifies the Smith set via graph Strongly Connected Component (SCC) analysis of
|
||||
the pairwise majority graph using [`rustworkx`](https://www.rustworkx.org/).
|
||||
Pairwise majority comparisons scale quadratically in the number of candidates and linearly
|
||||
in the number of ballots, while the SCC and condensation graph analysis is
|
||||
approximately quadratic in the number of candidates for the dense tournament graphs typical
|
||||
of Condorcet elections. Internally, repeated ballots are compressed/cache-counted before
|
||||
pairwise evaluation to improve performance over duplicate rankings.
|
||||
This is all overkill for small elections, but is fun.
|
||||
|
||||
Optionally, `smithy` can try to further resolve a nontrivial Smith set (a majoritarian tie or cycle) by running all-paths IRV within the set - at least reducing to an IRV winner set (the set of candidates that win at least one IRV elimination path) within the Smith set that are not only pairwise competitive but can also build competitive plurality
|
||||
coalitions within the set; in practice this often resolves cycles and is likely to result in a unique delegate (if they win all IRV elimination paths) which can claim a plurality coalition amongst the majority winners. This should identify a winning candidate as the best possible focal point for voluntary coordination; if your elections have other priorities you may want to resolve nontrivial Smith sets differently.
|
||||
|
||||
|
||||
## Usage
|
||||
`smithy` can be used a few different ways: as a CLI command, via a web form upload,
|
||||
or imported as a python package (TODO: a small [GUI](https://doc.qt.io/qtforpython-6/)).
|
||||
|
||||
|
||||
### CLI Command
|
||||
`smithy` provides a [`click`](https://click.palletsprojects.com/en/stable/)
|
||||
CLI command defined in `src/smithycmd.py` which can be invoked a couple of ways:
|
||||
|
||||
- The [GitHub Releases Page](https://github.com/tgorordo/smithy/releases) provides standalone
|
||||
CLI executables bundled using [PyInstaller](https://pyinstaller.org/en/stable/index.html)
|
||||
for linux and windows on `x86_64` and linux on `aarch64`. Downloading the appropriate
|
||||
executable should give you access to the `smithycmd` CLI command.
|
||||
|
||||
- Alternatively, if you are using `uv` (see [Development](#Development)), you can invoke
|
||||
this in your shell as `uv run src/smithycmd.py [...]` from within the repo after cloning it locally.
|
||||
|
||||
In either case, the command expects the same argument structure:
|
||||
```bash
|
||||
$ uv run src/smithycmd.py --help
|
||||
Usage: smithycmd.py [OPTIONS] BALLOTS
|
||||
|
||||
Compute the Smith set from a box of ranked-choice ballots -- .csv or
|
||||
.xls(x).
|
||||
|
||||
The Smith set is the minimal set of candidates which can beat all others
|
||||
pairwise (simple ranking majority) - if there is a single winner in the set,
|
||||
they are guaranteed the Condorcet i.e. Majority winner.
|
||||
|
||||
Options:
|
||||
-b, --show-ballots Show relevant ballots (after selections).
|
||||
-p, --pretty Pretty-print output.
|
||||
--help Show this message and exit.
|
||||
```
|
||||
where the ballots file should be a `.csv` or `.xls`(`x`) whose columns are candidates and whose
|
||||
rows are numerical RCV ballot rankings (lower number is best, 1st, 2nd, 3rd, etc.).
|
||||
(see the [web deployment](https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi/form.html)
|
||||
or `test/` for some examples).
|
||||
|
||||
If you want a 'None' option in your election, it should be included as a candidate.
|
||||
|
||||
While plain output is the default, so that the command can easily be used in a unix-pipe
|
||||
or `stdio` workflows, it can also pretty-print its output for your reading pleasure
|
||||
using [`rich`](https://rich.readthedocs.io/en/stable/introduction.html) if you pass it the
|
||||
`--pretty` (or `-p`) flag. Whether to show the input ballots during output is also
|
||||
controlled by a corresponding flag.
|
||||
|
||||
|
||||
### UOregon CGI Application
|
||||
[**This** `pages.uoregon.edu` page](https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi/form.html)
|
||||
hosts a [CGI form](https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069)
|
||||
which accepts a `.csv` or `.xls`(`x`) upload of a ballot-box and responds with the resulting
|
||||
Smith set. The application can typically handle ~100s of candidates and ~10ks of votes
|
||||
before running into hosting resource limitations and timing out.
|
||||
|
||||
No ballot data is retained on the server after execution.
|
||||
|
||||
|
||||
### Python Package
|
||||
The `smithy` python package primarily provides the function `smith_set(ballot_box_df)`
|
||||
(defined in `src/smithy`), which returns the smith set given a
|
||||
[polars](https://docs.pola.rs/api/python/stable/reference/index.html)
|
||||
dataframe `ballot_box_df` whose columns are candidates and whose rows are RCV ballots.
|
||||
|
||||
e.g. `smithy` can be imported into a [marimo](https://docs.marimo.io/#highlights) notebook
|
||||
as seen in `test/test_nb.py` - if you're using `uv` you can launch marimo via
|
||||
`uv run marimo --edit` and navigate to that example notebook
|
||||
(`just marimo` is an available shorthand if you are using the `just` taskrunner --
|
||||
see [Development](#Development)). Cloning `smithy` then working in scratch notebooks within the repo
|
||||
(e.g. makeing your own `nbs/` directory)
|
||||
is probably the easiest way to use it if you need to do some of cleanup of the tabular
|
||||
data before invocation - just do your cleanup in a notebook using
|
||||
[polars](https://docs.pola.rs/py-polars/html/reference/) then pass a tidy dataframe to `smith_set(df)`.
|
||||
|
||||
Of course, you can also import into any python script or package for a more involved project
|
||||
as well:
|
||||
- If you're using `uv` adding `smithy` should be as simple as
|
||||
```bash
|
||||
uv add git+https://github.com/tgorordo/smithy.git
|
||||
```
|
||||
- Alternatively, if you're using the more default `pip` you can make it available by
|
||||
```bash
|
||||
git clone https://github.com/tgorordo/smithy.git # or submodule add somewhere appropriate in your project
|
||||
cd smithy
|
||||
pip install .
|
||||
```
|
||||
(if you're new to pip-venvs,
|
||||
[this brief guide might be helpful](https://pages.uoregon.edu/tgorordo/courses/uoph410-510a_Image-Analysis/setup.html)).
|
||||
|
||||
|
||||
(The core algorithm is also pretty dead simple, and you could just copy it over into your project too).
|
||||
|
||||
## Development
|
||||
Current development tooling is based on the [`uv`](https://docs.astral.sh/uv/) Python package and
|
||||
project manager. A [`justfile`](https://github.com/casey/just) lists some common useful
|
||||
task commands. A simple [`shell.nix`](https://nix.dev/tutorials/first-steps/declarative-shell.html)
|
||||
can be used to load these tools into a local environment, and you might find
|
||||
[`direnv`](https://github.com/direnv/direnv) a convenient pairing to `nix` - but just having a
|
||||
system-provided `uv` available is more than enough to use and develop `smithy`.
|
||||
|
||||
|
||||
|
||||
### Formatting, Checking, Testing and Locking
|
||||
Source can be formatted using [`ruff`](https://docs.astral.sh/ruff/) by running
|
||||
```bash
|
||||
uv run ruff format src test
|
||||
```
|
||||
(or `just format`)
|
||||
which should be done before any `git` commit.
|
||||
|
||||
This is not really taken full advantage of at the moment, but type hints can be checked
|
||||
by running [pyright](https://github.com/microsoft/pyright)
|
||||
```bash
|
||||
uv run pyright src
|
||||
```
|
||||
(or `just check`).
|
||||
|
||||
A [pytest](https://docs.pytest.org/en/stable/) test suite can be run as
|
||||
```bash
|
||||
uv run pytest -vvv --tb=short --log-cli-level=INFO
|
||||
```
|
||||
(or `just test`).
|
||||
|
||||
Dependencies can be locked by running
|
||||
```bash
|
||||
uv lock
|
||||
uv pip compile pyproject.toml -o requirements.txt --group dev
|
||||
```
|
||||
(or `just lock`).
|
||||
|
||||
|
||||
### Bundling, Releases, Deployment and Cleanup
|
||||
Bundling the CLI command with [PyInstaller](https://pyinstaller.org/en/stable/index.html) can be done by
|
||||
```bash
|
||||
uv run --with-requirements src/smithycmd.py pyinstaller --clean -F src/smithycmd.py
|
||||
```
|
||||
(or `just compile`)
|
||||
which will output an executable `smithycmd` for the CLI in `dist/`, which can be uploaded
|
||||
to the latest [GitHub Releases Page](https://github.com/tgorordo/smithy/releases).
|
||||
|
||||
Deploying the main CGI page at
|
||||
[`pages.uoregon.edu/tgorordo/files/smithy/src/cgi`](https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi)
|
||||
requires shell or SFTP access to [my `pages.uoregon.edu`](https://pages.uoregon.edu/tgorordo),
|
||||
which you won't get unless you're [me](https://github.com/tgorordo).
|
||||
|
||||
If you want to deploy it to your own `pages.uoregon.edu` page, then it should be enough to
|
||||
ensure you have [CGI set up](https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069)
|
||||
then SFTP into `sftp.uoregon.edu` and `put -r smithy` (or git clone when `ssh`ed into
|
||||
`shell.uoregon.edu`) wherever you'd like in your `public_html`, ensure `smithy.cgi` has
|
||||
`chmod 755` permissions,
|
||||
[standalone install `uv` for your user](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
then run `uv sync` from somewhere inside the `smithy` directory. Navigating to the `smithy.cgi`
|
||||
file in `pages.uoregon.edu/YOUR_USER/.../cgi/smithy.cgi` should give you a working form.
|
||||
|
||||
If you're using `just` you can cleanup working files by running `just clean` and can fully
|
||||
wipe your `uv` generated `.venv` with `just wipe` (if you want manual versions of those,
|
||||
check the `justfile` definition). It's probably best to do this before any commit as well,
|
||||
though probably not required if you're careful or have `.gitignore` updated appropriately
|
||||
for any changes you made.
|
||||
|
||||
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
||||
in the set they are guaranteed the standard Condorcet i.e. Majority winner (they beat all others pairwise).
|
||||
|
|
|
|||
29
justfile
29
justfile
|
|
@ -1,8 +1,20 @@
|
|||
list:
|
||||
just --list
|
||||
just --list --unsorted
|
||||
|
||||
run spreadsheet:
|
||||
uv run smithy {{spreadsheet}}
|
||||
run *args:
|
||||
uv run src/smithycmd.py {{args}}
|
||||
|
||||
python *arguments:
|
||||
uv run python -c {{arguments}}
|
||||
|
||||
marimo:
|
||||
uv run marimo --edit
|
||||
|
||||
sync *args:
|
||||
uv sync {{args}}
|
||||
|
||||
format:
|
||||
uv run ruff format src test
|
||||
|
||||
check:
|
||||
uv run pyright src
|
||||
|
|
@ -10,20 +22,13 @@ check:
|
|||
test:
|
||||
uv run pytest -vvv --tb=short --log-cli-level=INFO
|
||||
|
||||
format:
|
||||
uv run ruff format src test
|
||||
|
||||
example:
|
||||
uv run python src/main.py test/test_ballot.csv
|
||||
|
||||
compile:
|
||||
uv run pyinstaller --clean -F src/main.py --name smithy
|
||||
uv run --with-requirements src/smithycmd.py pyinstaller --clean -F src/smithycmd.py
|
||||
|
||||
|
||||
clean:
|
||||
uv run pyclean src test
|
||||
uv run ruff clean
|
||||
rm -rf main.spec cli.spec build dist .pytest_cache .hypothesis .benchmarks __marimo__
|
||||
rm -rf smithycmd.spec build dist .pytest_cache .hypothesis .benchmarks __marimo__
|
||||
|
||||
wipe:
|
||||
just clean
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "smithy"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
|
@ -8,13 +8,19 @@ authors = [
|
|||
]
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"click>=8.4.0",
|
||||
"numpy>=2.4.6",
|
||||
"polars>=1.40.1",
|
||||
"rustworkx>=0.17.1",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
cli = [
|
||||
"click>=8.4.0",
|
||||
"rich>=15.0.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
smithy = "smithy:cli"
|
||||
#[project.scripts]
|
||||
#smithy = "smithycmd:cli"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.11.7,<0.12.0"]
|
||||
|
|
@ -33,3 +39,11 @@ dev = [
|
|||
"pytest-benchmark>=5.2.3",
|
||||
"ruff>=0.15.13",
|
||||
]
|
||||
|
||||
[pytest]
|
||||
testpaths = "test"
|
||||
log_cli = true
|
||||
|
||||
[tool.pyright]
|
||||
include = ["src"]
|
||||
exclude = ["test"]
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ charset-normalizer==3.4.7
|
|||
# via requests
|
||||
click==8.4.0
|
||||
# via
|
||||
# smithy (pyproject.toml)
|
||||
# marimo
|
||||
# uvicorn
|
||||
distro==1.9.0
|
||||
|
|
@ -84,12 +83,8 @@ markdown==3.10.2
|
|||
# via
|
||||
# marimo
|
||||
# pymdown-extensions
|
||||
markdown-it-py==4.2.0
|
||||
# via rich
|
||||
markupsafe==3.0.3
|
||||
# via jinja2
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
msgspec==0.21.1
|
||||
# via marimo
|
||||
narwhals==2.21.2
|
||||
|
|
@ -100,6 +95,10 @@ nbformat==5.10.4
|
|||
# via marimo
|
||||
nodeenv==1.10.0
|
||||
# via pyright
|
||||
numpy==2.4.6
|
||||
# via
|
||||
# smithy (pyproject.toml)
|
||||
# rustworkx
|
||||
openai==2.37.0
|
||||
# via pydantic-ai-slim
|
||||
opentelemetry-api==1.42.0
|
||||
|
|
@ -151,7 +150,6 @@ pygments==2.20.0
|
|||
# via
|
||||
# marimo
|
||||
# pytest
|
||||
# rich
|
||||
pyinstaller==6.20.0
|
||||
# via smithy (pyproject.toml:dev)
|
||||
pyinstaller-hooks-contrib==2026.5
|
||||
|
|
@ -184,8 +182,6 @@ regex==2026.5.9
|
|||
# via tiktoken
|
||||
requests==2.34.2
|
||||
# via tiktoken
|
||||
rich==15.0.0
|
||||
# via smithy (pyproject.toml)
|
||||
rpds-py==0.30.0
|
||||
# via
|
||||
# jsonschema
|
||||
|
|
@ -194,6 +190,8 @@ ruff==0.15.13
|
|||
# via
|
||||
# smithy (pyproject.toml:dev)
|
||||
# marimo
|
||||
rustworkx==0.17.1
|
||||
# via smithy (pyproject.toml)
|
||||
setuptools==82.0.1
|
||||
# via
|
||||
# pyinstaller
|
||||
|
|
|
|||
38
smithy.spec
38
smithy.spec
|
|
@ -1,38 +0,0 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['src/main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='smithy',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
103
src/cgi/form.html
Normal file
103
src/cgi/form.html
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> Smithy - RCV Ballot Counter </title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
input[type="file"] {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
input[type="submit"] {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Smithy: RCV Ballot Counter - CGI Application Upload</h1>
|
||||
|
||||
<div class="form-container">
|
||||
<form action="./smithy.cgi" method="POST" enctype="multipart/form-data">
|
||||
<label for="spreadsheet">Upload a ballot-box spreadsheet (.xlsx, .xls, .csv):</label>
|
||||
<input type="file" id="spreadsheet" name="spreadsheet" accept=".xlsx,.xls,.csv" required>
|
||||
<br>
|
||||
<input type="submit" value="Upload and Solve!">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="explanation-container">
|
||||
|
||||
<h2>About</h2>
|
||||
<p>This is an upload form for the <a href="https://github.com/tgorordo/smithy">smithy</a> RCV Ballot counter for identifying the Smith set (majority winners) in small elections,
|
||||
mainly to help with <a href="https://blogs.uoregon.edu/physicsgsg/">UO Physics Grad student elections</a> of various flavors. This form uses the <a href="https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069">UO pages.uoregon.edu CGI Capability</a>,
|
||||
so the implementation of smithy being invoked can be <a href="https://pages.uoregon.edu/tgorordo/files/smithy/"> inspected here</a>
|
||||
and you may also inspect the source of this page to verify that <a href="https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi/smithy.cgi?source">this script<a> is called to invoke it - though
|
||||
you have to trust it to <a href="https://en.wikipedia.org/wiki/Quine_(computing)">quine</a> itself faithfully.
|
||||
</p>
|
||||
|
||||
<p> The <a href="https://en.wikipedia.org/wiki/Smith_set">Smith set</a> is the minimal set of candidates which can beat all others pairwise (by majority ranking)
|
||||
- if there is a single winner in the set they are guaranteed the standard <a href=https://en.wikipedia.org/wiki/Condorcet_winner>Condorcet i.e. Majority winner</a>
|
||||
(they beat all others pairwise).</p>
|
||||
|
||||
<h3>Input: Expected Spreadsheet Format</h3>
|
||||
<p>A ballot-box spreadsheet should be organized by columns as candidates and rows as numerical rankings of the candidates -
|
||||
i.e. each row is the ranking provided by the ballot of a voter,
|
||||
e.g.:</p>
|
||||
|
||||
<p><a href="../../test/test_ballot.csv">test_ballot.csv<a>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Alice</th>
|
||||
<th>Bob</th>
|
||||
<th>Charlie</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>1</td>
|
||||
<td>3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>3</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
</table>
|
||||
where this reads that the first voter had preferences "Alice" over "Bob" over "Charlie", the second voter "Bob" over "Alice" over "Charlie", etc.
|
||||
(This example is constructed to demonstrate a situation without a singular majority winner - its Smith set a tie between "Alice" and "Bob"). Rankings may contain ties by e.g. repeating a numerical rank.</p>
|
||||
|
||||
<h3>Output: Smith Set Format</h3>
|
||||
<p>The form will return a list of the Smith-set winners sorted lexicographically (they are all equally good majority winners).
|
||||
It might be helpful for clarity to provide the input sheet's columns already in lexicographic order, but this is not required. </p>
|
||||
</div>
|
||||
</body>
|
||||
<footer>
|
||||
<hr>
|
||||
<p>Author: <a href="https://pages.uoregon.edu/tgorordo">Thomas (Tom) C. Gorordo</a>
|
||||
Source: <a href="https://github.com/tgorordo/pages.uoregon.edu">pages.uoregon.edu/tgorordo</a>,
|
||||
<a href="https://github.com/tgorordo/smithy">smithy</a></p>
|
||||
</footer>
|
||||
</html>
|
||||
144
src/cgi/script.py
Normal file
144
src/cgi/script.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import traceback
|
||||
import sys, os
|
||||
import html
|
||||
|
||||
import re
|
||||
|
||||
import polars as pl
|
||||
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "../smithy/src")
|
||||
),
|
||||
)
|
||||
from smithy import smith_set_from_rcv
|
||||
|
||||
print("Content-Type: text/html\n")
|
||||
message = ""
|
||||
|
||||
content_type = os.environ.get("CONTENT_TYPE", "")
|
||||
content_length = int(os.environ.get("CONTENT_LENGTH", 0) or 0)
|
||||
boundary = content_type.split("boundary=")[-1].encode()
|
||||
|
||||
body = sys.stdin.buffer.read(content_length)
|
||||
parts = body.split(b"--" + boundary)
|
||||
|
||||
spreadsheet = None
|
||||
|
||||
for part in parts:
|
||||
if b"Content-Disposition" in part and b'name="spreadsheet"' in part:
|
||||
header, _, data = part.partition(b"\r\n\r\n")
|
||||
|
||||
filename_match = re.search(rb'filename="([^"]+)"', header)
|
||||
if filename_match:
|
||||
filename = filename_match.group(1).decode()
|
||||
filedata = data.rstrip(b"\r\n--")
|
||||
|
||||
spreadsheet = (filename, filedata)
|
||||
break
|
||||
|
||||
|
||||
if spreadsheet is not None:
|
||||
filename, filedata = spreadsheet
|
||||
|
||||
if filename and filedata:
|
||||
filepath = os.path.join("/tmp", filename)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(filedata)
|
||||
|
||||
try:
|
||||
if filename.endswith(".csv"):
|
||||
df = pl.read_csv(filepath)
|
||||
elif filename.endswith((".xlsx", ".xls")):
|
||||
df = pl.read_excel(filepath)
|
||||
else:
|
||||
message = """
|
||||
<h1>Error</h1>
|
||||
<p>File extension is not valid. Use CSV (.csv) or Excel (.xlsx, .xls).</p>
|
||||
<p><a href="form.html">Go Back</a></p>
|
||||
"""
|
||||
|
||||
if df is not None:
|
||||
# Normalize
|
||||
df = df.with_columns(
|
||||
[
|
||||
pl.col(c)
|
||||
.cast(pl.Utf8)
|
||||
.str.strip_chars()
|
||||
.cast(pl.Int64, strict=False)
|
||||
.fill_null(0)
|
||||
for c in df.columns
|
||||
]
|
||||
)
|
||||
|
||||
smiths = smith_set_from_rcv(df) # Solve!
|
||||
|
||||
message = f"""
|
||||
<h1>The Smith set winners are:</h1>
|
||||
{smiths}
|
||||
<p><a href="form.html">Go Back</a></p>
|
||||
"""
|
||||
else:
|
||||
message = """
|
||||
<h1>Error</h1>
|
||||
<p>DataFrame was empty.</p>
|
||||
<p><a href="form.html">Go Back</a></p>
|
||||
"""
|
||||
|
||||
except Exception as e:
|
||||
message = f"""
|
||||
<h1>Error</h1>
|
||||
<p>Internal Error Encountered: {e}
|
||||
<p><a href="form.html">Go Back</a></p>
|
||||
"""
|
||||
traceback.print_exc()
|
||||
|
||||
else:
|
||||
message = """
|
||||
<h1>Error</h1>
|
||||
<p>Filename or File Data not found/valid in form submission.</p>
|
||||
<p><a href="form.html">Go Back</a></p>
|
||||
"""
|
||||
|
||||
else:
|
||||
message = """
|
||||
<h1>Error</h1>
|
||||
<p>No file field found in the form.</p>
|
||||
<p><a href="form.html">Go Back</a></p>
|
||||
"""
|
||||
|
||||
print("""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> Smithy - RCV Ballot Counter </title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
""")
|
||||
print(message)
|
||||
print("""
|
||||
</body>
|
||||
<footer>
|
||||
<hr>
|
||||
<p>Author: <a href="https://pages.uoregon.edu/tgorordo">Thomas (Tom) C. Gorordo</a>
|
||||
Source: <a href="https://github.com/tgorordo/pages.uoregon.edu">pages.uoregon.edu/tgorordo</a>,
|
||||
<a href="https://github.com/tgorordo/smithy">smithy</a></p>
|
||||
</footer>
|
||||
</html>
|
||||
""")
|
||||
12
src/cgi/smithy.cgi
Executable file
12
src/cgi/smithy.cgi
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
if [[ "$QUERY_STRING" == "source" ]]; then
|
||||
echo "Content-Type: text/plain"
|
||||
echo
|
||||
sed 's/&/\&/g; s/</\</g; s/>/\>/g' "$0"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec "$SCRIPT_DIR/../../.venv/bin/python" "$SCRIPT_DIR/script.py"
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
from smithy import cli
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
|
@ -1,77 +1,79 @@
|
|||
import click
|
||||
import polars as pl
|
||||
import rustworkx as rwx
|
||||
from itertools import combinations
|
||||
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
|
||||
from .rcv import smith_set
|
||||
from .rcv import pmg_from_rcv
|
||||
from .irv import irv_from_rcv
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("spreadsheet", type=click.Path(exists=True, dir_okay=False))
|
||||
def cli(spreadsheet: str) -> None:
|
||||
def ss_from_pmg(pmg: rwx.PyDiGraph) -> list[str]:
|
||||
"""
|
||||
Compute the Smith set from a ranked-choice ballot spreadsheet.
|
||||
Find the Smith set from a pairwise majority graph.
|
||||
|
||||
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
||||
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
||||
parameters
|
||||
---
|
||||
pmg: rwx.PyDiGraph
|
||||
A graph whose nodes correspond to candidates and (directed) edges show
|
||||
which candidates they beat pairwise.
|
||||
|
||||
returns
|
||||
---
|
||||
smith_set: list
|
||||
A list of the Smith set candidates - all are equally good winners;
|
||||
ordering is determined lexicographically. If there is a Condorcet winner
|
||||
(single Majority winner), the Smith set will contain that single candidate.
|
||||
"""
|
||||
|
||||
console = Console()
|
||||
sccs = rwx.strongly_connected_components(pmg)
|
||||
|
||||
try:
|
||||
# Load spreadsheet
|
||||
if spreadsheet.endswith(".csv"):
|
||||
df = pl.read_csv(spreadsheet)
|
||||
cg = rwx.condensation(pmg, sccs)
|
||||
|
||||
elif spreadsheet.endswith((".xlsx", ".xls")):
|
||||
df = pl.read_excel(spreadsheet)
|
||||
src_sccs = [nd for nd in cg.node_indices() if cg.in_degree(nd) == 0]
|
||||
|
||||
else:
|
||||
console.print(
|
||||
"[bold red]Unsupported file type.[/bold red]\nUse CSV or Excel."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
smith_set = sorted([c for scc in src_sccs for c in cg[scc]])
|
||||
|
||||
# Normalize numerical dataframe entries
|
||||
df = df.with_columns(
|
||||
[
|
||||
pl.col(c)
|
||||
.cast(pl.Utf8)
|
||||
.str.strip_chars()
|
||||
.cast(pl.Int64, strict=False)
|
||||
.fill_null(0)
|
||||
for c in df.columns
|
||||
]
|
||||
return smith_set
|
||||
|
||||
|
||||
def smith_set_from_rcv(ballots: pl.DataFrame) -> list:
|
||||
"""
|
||||
Compute the Smith set from a Ranked-Choice ballot.
|
||||
|
||||
The Smith set is the minimal set of candidates which can beat all others pairwise -
|
||||
if there is a single winner in the set they are guaranteed the Condorcet i.e. Majority winner.
|
||||
|
||||
parameters
|
||||
---
|
||||
ballots : pl.DataFrame
|
||||
A Polars DataFrame representing ballots. Each column is a candidate and each
|
||||
row is is a voter's ranking of the candidates. Lower numbers indicate higher
|
||||
preference (1 = top-choice).
|
||||
|
||||
returns
|
||||
---
|
||||
smith_set : list
|
||||
A list of the Smith set candidates - all are equally good winners;
|
||||
ordering is determined lexicographically. If there is a Condorcet winner
|
||||
(single Majority winner), the Smith set will contain that single candidate.
|
||||
|
||||
"""
|
||||
|
||||
return ss_from_pmg(pmg_from_rcv(ballots))
|
||||
|
||||
|
||||
def smith_set(df: pl.DataFrame, ballotkind="rcv") -> list:
|
||||
if ballotkind == "rcv":
|
||||
return smith_set_from_rcv(df)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"`smith_set` ballotkind={ballotkind} is not implemented."
|
||||
)
|
||||
|
||||
# Compute Smith set
|
||||
smiths = smith_set(df)
|
||||
|
||||
# Preview table
|
||||
preview = Table(title="Ballot Box")
|
||||
|
||||
for col in df.columns:
|
||||
preview.add_column(col)
|
||||
|
||||
for row in df.head(5).iter_rows():
|
||||
preview.add_row(*map(str, row))
|
||||
|
||||
console.print(preview)
|
||||
|
||||
# Results
|
||||
console.print()
|
||||
|
||||
console.print(
|
||||
Panel.fit(
|
||||
"\n".join(f"• {c}" for c in smiths),
|
||||
title="Resulting Smith Set",
|
||||
border_style="green",
|
||||
)
|
||||
def irv_set(df: pl.DataFrame, ballotkind="rcv") -> list:
|
||||
if ballotkind == "rcv":
|
||||
return irv_from_rcv(df)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"`irv_set` ballotkind={ballotkind} is not implemented."
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
raise SystemExit(1)
|
||||
|
|
|
|||
102
src/smithy/irv.py
Normal file
102
src/smithy/irv.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import polars as pl
|
||||
import numpy as np
|
||||
|
||||
|
||||
def irv_from_rcv(ballots: pl.DataFrame, method: str = "bigslow") -> list[str]:
|
||||
"""
|
||||
Compute the set of all-paths IRV winners from an RCV ballot.
|
||||
|
||||
parameters
|
||||
---
|
||||
ballots: pl.DataFrame
|
||||
An RCV table of ballots.
|
||||
|
||||
method: str
|
||||
Either "bigslow" or "smallfast" for selecting an internal method for counting
|
||||
first-choices during IRV rounds. Defaults to "bigslow" but you can use "smallfast"
|
||||
so long as the table of ballots is expected to fit in a reasonable numpy array (after compression).
|
||||
|
||||
returns
|
||||
---
|
||||
winners: list[srt]
|
||||
A lexicographically sorted list of IRV winners. If a candidate wins every elimination path
|
||||
then this set will contain only one entry, otherwise it will contain all candidates that win
|
||||
at least one IRV elimination path.
|
||||
"""
|
||||
compressed = ballots.group_by(ballots.columns).len().rename({"len": "count"})
|
||||
return sorted(_irv_winners(compressed, method=method))
|
||||
|
||||
|
||||
def _fst_counts_bigslow(compressed: pl.DataFrame) -> pl.DataFrame:
|
||||
|
||||
surviving = [c for c in compressed.columns if c != "count"]
|
||||
|
||||
fstcexpr = (
|
||||
pl.concat_list([pl.col(c) for c in surviving])
|
||||
.list.arg_min().map_elements(lambda i: surviving[i], return_dtype=pl.String).alias("first_choice")
|
||||
)
|
||||
|
||||
tally = (
|
||||
compressed.with_columns(fstcexpr)
|
||||
.group_by("first_choice")
|
||||
.agg(pl.col("count").sum())
|
||||
.filter(pl.col("first_choice").is_not_null())
|
||||
)
|
||||
return tally
|
||||
|
||||
|
||||
def _fst_counts_smallfast(compressed: pl.DataFrame) -> pl.DataFrame:
|
||||
|
||||
surviving = [c for c in compressed.columns if c != "count"]
|
||||
|
||||
a = compressed.select(surviving).to_numpy()
|
||||
|
||||
cs = compressed["count"].to_numpy()
|
||||
|
||||
fstc_idxs = np.argmin(a, axis=1)
|
||||
|
||||
tally = {c: 0 for c in surviving}
|
||||
for i, c in zip(fstc_idxs, cs):
|
||||
tally[surviving[i]] += int(c)
|
||||
|
||||
return pl.DataFrame(
|
||||
{"first_choice": surviving, "count": [tally[c] for c in surviving]}
|
||||
)
|
||||
|
||||
|
||||
def _irv_round(compressed: pl.DataFrame, method="bigslow"):
|
||||
|
||||
if method == "bigslow":
|
||||
count_fn = _fst_counts_bigslow
|
||||
elif method == "smallfast":
|
||||
count_fn = _fst_counts_smallfast
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Error: _fst_counts method={method} not implemented."
|
||||
)
|
||||
|
||||
tally = count_fn(compressed)
|
||||
|
||||
eliminate = tally.filter(pl.col("count") == pl.col("count").min())[
|
||||
"first_choice"
|
||||
].to_list()
|
||||
|
||||
for e in eliminate:
|
||||
surviving = [c for c in compressed.columns if c not in ("count", e)]
|
||||
yield (
|
||||
compressed.select(surviving + ["count"])
|
||||
.group_by(surviving)
|
||||
.agg(pl.col("count").sum())
|
||||
)
|
||||
|
||||
|
||||
def _irv_winners(compressed, method="bigslow"):
|
||||
|
||||
surviving = [c for c in compressed.columns if c != "count"]
|
||||
if len(surviving) == 1:
|
||||
return set(surviving)
|
||||
|
||||
winners = set()
|
||||
for branch in _irv_round(compressed, method=method):
|
||||
winners |= _irv_winners(branch, method=method)
|
||||
return winners
|
||||
|
|
@ -1,64 +1,118 @@
|
|||
import polars as pl
|
||||
import rustworkx as rwx
|
||||
from itertools import combinations
|
||||
|
||||
|
||||
def smith_set(df: pl.DataFrame) -> list:
|
||||
def pmg_from_rcv_bigslow(ballots: pl.DataFrame) -> rwx.PyDiGraph:
|
||||
"""
|
||||
Compute the Smith set from a Ranked-Choice ballot.
|
||||
|
||||
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
||||
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
||||
Build a pairwise majority winner graph from a box of Ranked-Choice Ballots.
|
||||
|
||||
parameters
|
||||
---
|
||||
df : pl.DataFrame
|
||||
rcv_ballots : pl.DataFrame
|
||||
A Polars DataFrame representing ballots. Each column is a candidate and each
|
||||
row is is a voter's ranking of the candidates. Lower numbers indicate higher
|
||||
preference (1 = top-choice).
|
||||
|
||||
returns
|
||||
---
|
||||
smith_set : list
|
||||
A list of the Smith set candidates - all are equally good winners; ordering is determined lexicographically.
|
||||
If there is a Condorcet winner (single Majority winner), the Smith set will contain that single candidate.
|
||||
|
||||
nodes: dict[str, int]
|
||||
A dictionary of candidate names to associated node ids.
|
||||
|
||||
pwm_graph: rwx.PyDiGraph
|
||||
A pairwise majority winner graph whose nodes correspond to candidates and
|
||||
(directed) edges show which candidates they beat pairwise.
|
||||
"""
|
||||
candidates = ballots.columns
|
||||
|
||||
candidates = df.columns
|
||||
pmg = rwx.PyDiGraph()
|
||||
nodes = {c: pmg.add_node(c) for c in candidates}
|
||||
|
||||
# Build pairwise majority graph
|
||||
graph: dict[str, set[str]] = {c: set() for c in candidates}
|
||||
compressed = ballots.group_by(ballots.columns).len().rename({"len": "count"})
|
||||
|
||||
for a, b in combinations(candidates, 2):
|
||||
result = df.select(
|
||||
exprs = []
|
||||
pairs = list(combinations(candidates, 2))
|
||||
|
||||
for a, b in pairs:
|
||||
exprs.extend(
|
||||
[
|
||||
(pl.col(a) < pl.col(b)).sum().alias("a_wins"),
|
||||
(pl.col(b) < pl.col(a)).sum().alias("b_wins"),
|
||||
pl.when(pl.col(a) < pl.col(b))
|
||||
.then(pl.col("count"))
|
||||
.otherwise(0)
|
||||
.sum()
|
||||
.alias(f"{a}>{b}"),
|
||||
pl.when(pl.col(b) < pl.col(a))
|
||||
.then(pl.col("count"))
|
||||
.otherwise(0)
|
||||
.sum()
|
||||
.alias(f"{b}>{a}"),
|
||||
]
|
||||
).row(0)
|
||||
)
|
||||
|
||||
a_wins, b_wins = result
|
||||
results = compressed.select(exprs).row(0, named=True)
|
||||
|
||||
for a, b in pairs:
|
||||
a_wins = results[f"{a}>{b}"]
|
||||
b_wins = results[f"{b}>{a}"]
|
||||
|
||||
if a_wins > b_wins:
|
||||
graph[a].add(b)
|
||||
pmg.add_edge(nodes[a], nodes[b], a_wins - b_wins)
|
||||
elif b_wins > a_wins:
|
||||
graph[b].add(a)
|
||||
pmg.add_edge(nodes[b], nodes[a], b_wins - a_wins)
|
||||
|
||||
# Find Smith set
|
||||
for size in range(1, len(candidates) + 1):
|
||||
for sub in combinations(candidates, size):
|
||||
subset = set(sub)
|
||||
out = set(candidates) - subset
|
||||
return pmg
|
||||
|
||||
dom = True
|
||||
|
||||
for member in subset:
|
||||
if not out.issubset(graph[member]):
|
||||
dom = False
|
||||
break
|
||||
def pmg_from_rcv_smallfast(ballots: pl.DataFrame) -> rwx.PyDiGraph:
|
||||
"""
|
||||
Build a pairwise majority winner graph from a box of Ranked-Choice Ballots.
|
||||
|
||||
if dom:
|
||||
return sorted(subset)
|
||||
parameters
|
||||
---
|
||||
rcv_ballots : pl.DataFrame
|
||||
A Polars DataFrame representing ballots. Each column is a candidate and each
|
||||
row is is a voter's ranking of the candidates. Lower numbers indicate higher
|
||||
preference (1 = top-choice).
|
||||
|
||||
return []
|
||||
returns
|
||||
---
|
||||
nodes: dict[str, int]
|
||||
A dictionary of candidate names to associated node ids.
|
||||
|
||||
pwm_graph: rwx.PyDiGraph
|
||||
A pairwise majority winner graph whose nodes correspond to candidates and
|
||||
(directed) edges show which candidates they beat pairwise.
|
||||
"""
|
||||
candidates = ballots.columns
|
||||
|
||||
pmg = rwx.PyDiGraph()
|
||||
nodes = {c: pmg.add_node(c) for c in candidates}
|
||||
|
||||
compressed = ballots.group_by(ballots.columns).len().rename({"len": "count"})
|
||||
counts = compressed["count"].to_numpy()
|
||||
|
||||
arr = compressed.drop("count").to_numpy()
|
||||
results = ((arr[:, :, None] < arr[:, None, :]) * counts[:, None, None]).sum(axis=0)
|
||||
|
||||
for i, a in enumerate(candidates):
|
||||
for j in range(i + 1, len(candidates)):
|
||||
b = candidates[j]
|
||||
|
||||
a_wins = results[i, j]
|
||||
b_wins = results[j, i]
|
||||
|
||||
if a_wins > b_wins:
|
||||
pmg.add_edge(nodes[a], nodes[b], int(a_wins - b_wins))
|
||||
elif b_wins > a_wins:
|
||||
pmg.add_edge(nodes[b], nodes[a], int(b_wins - a_wins))
|
||||
|
||||
return pmg
|
||||
|
||||
|
||||
def pmg_from_rcv(ballots: pl.DataFrame, method="bigslow") -> rwx.PyDiGraph:
|
||||
if method == "bigslow":
|
||||
return pmg_from_rcv_bigslow(ballots)
|
||||
elif method == "smallfast":
|
||||
return pmg_from_rcv_smallfast(ballots)
|
||||
else:
|
||||
raise NotImplementedError(f"`pmg_from_rcv` method={method} not implemented.")
|
||||
|
|
|
|||
115
src/smithycmd.py
Normal file
115
src/smithycmd.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# /// script
|
||||
# requires-python = ">=3.13"
|
||||
# dependencies = [
|
||||
# "click>=8.4.1",
|
||||
# "rich>=15.0.0",
|
||||
# "polars>=1.40.1",
|
||||
# "rustworkx>=0.17.1"
|
||||
# ]
|
||||
# ///
|
||||
import sys, io
|
||||
import click
|
||||
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
|
||||
import polars as pl
|
||||
from smithy import smith_set, irv_set
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("ballots", type=click.Path(exists=True, dir_okay=False))
|
||||
@click.option(
|
||||
"--try-resolve-irv",
|
||||
is_flag=True,
|
||||
help="Try to reduce or resolve the Smith set by running all-paths IRV on the set.",
|
||||
)
|
||||
@click.option(
|
||||
"--show-ballots",
|
||||
"-b",
|
||||
is_flag=True,
|
||||
help="Show relevant ballots (after selections).",
|
||||
)
|
||||
@click.option("--pretty", "-p", is_flag=True, help="Pretty-print output.")
|
||||
def cli(ballots: str, try_resolve_irv=False, show_ballots=False, pretty=False) -> None:
|
||||
"""
|
||||
Compute the Smith set from a box of ranked-choice ballots -- .csv or .xls(x).
|
||||
|
||||
The Smith set is the minimal set of candidates which can beat all others pairwise
|
||||
(simple ranking majority) - if there is a single winner in the set,
|
||||
they are guaranteed the Condorcet i.e. Majority winner.
|
||||
"""
|
||||
|
||||
console = Console()
|
||||
|
||||
try:
|
||||
# Load ballots
|
||||
if ballots.endswith(".csv"):
|
||||
df = pl.read_csv(ballots)
|
||||
|
||||
elif ballots.endswith((".xlsx", ".xls")):
|
||||
df = pl.read_excel(ballots)
|
||||
|
||||
else:
|
||||
console.print(
|
||||
"[bold red]Unsupported file type.[/bold red]\nUse CSV or Excel."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
# Normalize numerical dataframe entries
|
||||
df = df.with_columns(
|
||||
[
|
||||
pl.col(c)
|
||||
.cast(pl.String)
|
||||
.str.strip_chars()
|
||||
.cast(pl.Int64, strict=False)
|
||||
.fill_null(0)
|
||||
for c in df.columns
|
||||
]
|
||||
)
|
||||
|
||||
# Compute Smith set
|
||||
smiths = smith_set(df)
|
||||
if len(smiths) > 1 and try_resolve_irv:
|
||||
irv_ballots = df.select(smiths)
|
||||
smiths = irv_set(irv_ballots)
|
||||
|
||||
if show_ballots and pretty:
|
||||
preview = Table(title="Ballot Box")
|
||||
|
||||
for col in df.columns:
|
||||
preview.add_column(col)
|
||||
|
||||
for row in df.head(5).iter_rows():
|
||||
preview.add_row(*map(str, row))
|
||||
|
||||
console.print(preview)
|
||||
console.print()
|
||||
elif show_ballots and not pretty:
|
||||
buf = io.StringIO()
|
||||
df.write_csv(buf)
|
||||
console.print(buf.getvalue())
|
||||
console.print("---")
|
||||
|
||||
if pretty:
|
||||
console.print(
|
||||
Panel.fit(
|
||||
"\n".join(f"• {c}" for c in smiths),
|
||||
title="Resulting IRV-resolved Smith Set"
|
||||
if (try_resolve_irv)
|
||||
else "Resulting Smith Set",
|
||||
border_style="green",
|
||||
)
|
||||
)
|
||||
else:
|
||||
console.print(", ".join(f"{c}" for c in smiths))
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
18
src/smithygui.py
Normal file
18
src/smithygui.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# /// script
|
||||
# requires-python = ">=3.13"
|
||||
# dependencies = [
|
||||
# "click>=8.4.1",
|
||||
# "polars>=1.41.0",
|
||||
# "pyside6>=6.11.1",
|
||||
# "rich>=15.0.0",
|
||||
# "rustworkx>=0.17.1",
|
||||
# ]
|
||||
# ///
|
||||
from PySide6.QtWidgets import QApplication, QWidget
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
window = QWidget()
|
||||
window.show()
|
||||
|
||||
app.exec()
|
||||
1001
test/stress_test_1000000x1000.csv
Normal file
1001
test/stress_test_1000000x1000.csv
Normal file
File diff suppressed because one or more lines are too long
1001
test/stress_test_100000x100.csv
Normal file
1001
test/stress_test_100000x100.csv
Normal file
File diff suppressed because it is too large
Load diff
1001
test/stress_test_1000x100.csv
Normal file
1001
test/stress_test_1000x100.csv
Normal file
File diff suppressed because it is too large
Load diff
301
uv.lock
generated
301
uv.lock
generated
|
|
@ -170,14 +170,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.4.0"
|
||||
version = "8.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -240,15 +240,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "genai-prices"
|
||||
version = "0.0.61"
|
||||
version = "0.0.62"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx2" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/71/0c76010eec75f4b3623d521044785c0977c14adabe1cac72b004349567fb/genai_prices-0.0.61.tar.gz", hash = "sha256:4b3bcfd49f174c05831b09f9ee36557d3648569e2f594af6c24b72031b3f0e52", size = 67806, upload-time = "2026-05-19T17:01:36.902Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0c/8e/ed322d1f22b57fd455749bdbe2f285d310e1c1ebe921cb3d5c0b920de648/genai_prices-0.0.62.tar.gz", hash = "sha256:baf1ffa64be0d15577878216464d6a2d04244db5fbdf78d56bde43809e7aef44", size = 67611, upload-time = "2026-05-25T18:47:16.306Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/ec/b08dc2e834ca00fd8dfedcb17ae2e920667adaad617b45e32b7a3b146f24/genai_prices-0.0.61-py3-none-any.whl", hash = "sha256:d77142f61c13e69909ac19c8e44fd315fd65f3afd714e8d55e914fab0eaf47a2", size = 70853, upload-time = "2026-05-19T17:01:37.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/35/ce64112dcc6f406b3e290dcf57a97acfa2b7d3d0391979219cb9d4a9db6d/genai_prices-0.0.62-py3-none-any.whl", hash = "sha256:5d9ab0d9e5d81e035f88bf591fb6a8dde527922786acf1ee2737358f7bbe0167", size = 70333, upload-time = "2026-05-25T18:47:17.642Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -282,6 +282,19 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore2"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/7e/8ab39aab1d392845b6512009a9be57d24a5bd4ec7a22d02e513d0645e7a8/httpcore2-2.2.0.tar.gz", hash = "sha256:10e0e142f1ecc1c1cb2a9ebbce82e57f16169f61d163ea336abf36799e89294b", size = 63533, upload-time = "2026-05-17T05:29:55.836Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/22/64de17e7956e8c002f7558ed667d924c2a288344aeff4bd8ff5dc5fdb70b/httpcore2-2.2.0-py3-none-any.whl", hash = "sha256:ce859f268bf8d34fa2d7753e09e4dd5194f557e1b3038439b68a89b2999572fa", size = 79288, upload-time = "2026-05-17T05:29:52.56Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
|
|
@ -298,12 +311,27 @@ wheels = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.15"
|
||||
name = "httpx2"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore2" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/aa/c3119de1aa7ad870a01aaddbf3bc3445ed9a681c31d45e3838fd8b7bc155/httpx2-2.2.0.tar.gz", hash = "sha256:f3428d59b1752b8f5629826277262fb4d65e3a683f48af8a5b16c4d012e0b801", size = 80477, upload-time = "2026-05-17T05:29:57.376Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/e0/e0a52596c14194e428c20de4903f4abec38c0dfb5364d20f1d4a2b6266ef/httpx2-2.2.0-py3-none-any.whl", hash = "sha256:12347ebd2daeaefd50b529359778fff767082a09c5826752c963e71269722ff0", size = 74083, upload-time = "2026-05-17T05:29:54.543Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.16"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -517,7 +545,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "marimo"
|
||||
version = "0.23.6"
|
||||
version = "0.23.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
|
|
@ -532,6 +560,7 @@ dependencies = [
|
|||
{ name = "psutil" },
|
||||
{ name = "pygments" },
|
||||
{ name = "pymdown-extensions" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "pyzmq", marker = "python_full_version < '3.15'" },
|
||||
{ name = "starlette" },
|
||||
|
|
@ -539,9 +568,9 @@ dependencies = [
|
|||
{ name = "uvicorn" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/96/7f03859bfc88b9ee25f1562ad1e7e0284c442f2956eaf0f9c849836d378f/marimo-0.23.6.tar.gz", hash = "sha256:d63aeeee1e9ea7cac79bf2530daba915199153dce4d156fade7546474679d3ca", size = 38426356, upload-time = "2026-05-11T21:39:57.588Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/fe/a1dec6111a660ee3bd26521c24788e7aef519a31c60cf37f767f29313454/marimo-0.23.8.tar.gz", hash = "sha256:8049df4ad263e7126e959d7d910b014e6181dffe49f540a89c3174e61a446a99", size = 38505767, upload-time = "2026-05-22T16:24:19.881Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/44/a7/c19a6481dfe7762eb07a5bd0a7d7ae560a2ab5505a519bce159d0fd08beb/marimo-0.23.6-py3-none-any.whl", hash = "sha256:e8d19d875b0212600faa80eaaed0fd3f34dfb7b5241e630cf2b1cedd8dd14509", size = 38849553, upload-time = "2026-05-11T21:39:53.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/76/51b57a2e521b9110f25ec935f2774412e2f3d678b3fdea7841987244fb2c/marimo-0.23.8-py3-none-any.whl", hash = "sha256:99a4035d035fb320c8f2dcefc2213e0d64e9de13e989bc3f2a973b19dc40542a", size = 38938839, upload-time = "2026-05-22T16:24:16.102Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -704,9 +733,59 @@ 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 = "numpy"
|
||||
version = "2.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "2.37.0"
|
||||
version = "2.38.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -718,21 +797,21 @@ dependencies = [
|
|||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/32/50/5901f01ef14e6c27788beb91e54fef5d6204fb5fb9e97402fc8a14de2e32/openai-2.37.0.tar.gz", hash = "sha256:f4bc562cc5f3a43d40d678105572d9d44765f6e0f50c125f63055419b72f4bd9", size = 754706, upload-time = "2026-05-15T22:30:35.428Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/12/cfa322c5f5dd8fa21aab9a7a8e979e7a11123800f86ca8d82eb68a83d213/openai-2.38.0.tar.gz", hash = "sha256:798694c6cf74145541fda94325b6f8f72d8e1fd0262cc137c8d728177a6a4ce3", size = 772764, upload-time = "2026-05-21T21:23:42.105Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/4c/bce61680d0699a78a405fd9a67989b175ba020590428831aab2ab1d2be7c/openai-2.37.0-py3-none-any.whl", hash = "sha256:814633888b8f3b1ffd6615697c6e4ef93632d08b7c2e28c8c5ef3556e5a10107", size = 1303238, upload-time = "2026-05-15T22:30:32.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/bf/ccff9be562e24207716d04ef9dc931c76aff0c89a7265da43e2104d7fe06/openai-2.38.0-py3-none-any.whl", hash = "sha256:ec6661c57b2dcc47414a767e6e3335c7ed3d19c9696999283a3c82e95c756a3c", size = 1344910, upload-time = "2026-05-21T21:23:39.636Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-api"
|
||||
version = "1.42.0"
|
||||
version = "1.42.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/ca/25288069c399be6769159d9fb7b1190b603537d82aad2fa2746a0cc2c8c6/opentelemetry_api-1.42.0.tar.gz", hash = "sha256:ea84c893ad177791d138e0349d6ceebd8d3bf006440900400ce220008dafc372", size = 72300, upload-time = "2026-05-19T09:46:29.885Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b4/1c/125e1c936c0873796771b7f04f6c93b9f1bf5d424cea90fda94a99f61da8/opentelemetry_api-1.42.1.tar.gz", hash = "sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716", size = 72296, upload-time = "2026-05-21T16:32:49.335Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/0b/be5daf659b82b525338fde371dfcfab09b606a19bb5620c37076964710ec/opentelemetry_api-1.42.0-py3-none-any.whl", hash = "sha256:558d88f88192a973579910ef6f2c13db47a268d5ec2e53e83e50e74a39a02922", size = 61310, upload-time = "2026-05-19T09:46:06.561Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/ca/9520cc1f3dfbbd03ac5903bbf55833e257bc64b1cf30fa8b0d6df374d821/opentelemetry_api-1.42.1-py3-none-any.whl", hash = "sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714", size = 61311, upload-time = "2026-05-21T16:32:28.822Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -782,14 +861,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "polars"
|
||||
version = "1.40.1"
|
||||
version = "1.41.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "polars-runtime-32" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/8c/bc9bc948058348ed43117cecc3007cd608f395915dae8a00974579a5dab1/polars-1.40.1.tar.gz", hash = "sha256:ab2694134b137596b5a59bfd7b4c54ebbc9b59f9403127f18e32d363777552e8", size = 733574, upload-time = "2026-04-22T19:15:55.507Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/13/fe30b3e2f9ab54a27d82af04fb2edc51c7342cbaa88815e175769a9f5901/polars-1.41.0.tar.gz", hash = "sha256:7cb5465eb66eb868fde779bf5c41c9f2f244481d72c52133e8ed10ba64372e4f", size = 737530, upload-time = "2026-05-22T20:20:56.209Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/91/74fc60d94488685a92ac9d49d7ec55f3e91fe9b77942a6235a5fa7f249c3/polars-1.40.1-py3-none-any.whl", hash = "sha256:c0f861219d1319cdea45c4ce4d30355a47176b8f98dcedf95ea8269f131b8abd", size = 828723, upload-time = "2026-04-22T19:14:25.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c8/5807714256c5f3de08593113df17f14f99417a451cb2d91530ad94785003/polars-1.41.0-py3-none-any.whl", hash = "sha256:35dcd24de88a198dc50929924f064ba12a0a0a4a3e77e116689491b4b3ab58ac", size = 832953, upload-time = "2026-05-22T20:19:30.958Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -799,18 +878,18 @@ pyarrow = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-runtime-32"
|
||||
version = "1.40.1"
|
||||
version = "1.41.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ba/26d40f039be9f552b5fd7365a621bdfc0f8e912ef77094ae4693491b0bae/polars_runtime_32-1.40.1.tar.gz", hash = "sha256:37f3065615d1bf90d03b5326222df4c5c1f8a5d33e50470aa588e3465e6eb814", size = 2935843, upload-time = "2026-04-22T19:15:57.26Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/8f/30dc715ea1135b4b80397edf33fe7b1bb124850e96e38d9918e2b3d6d0b0/polars_runtime_32-1.41.0.tar.gz", hash = "sha256:37ffbe5414f14bf43bcc8e08a0386c97c692e3fd4e87af74529d7f14b1b2d1cb", size = 2985826, upload-time = "2026-05-22T20:20:57.622Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/46/22c8af5eed68ac2eeb556e0fa3ca8a7b798e984ceff4450888f3b5ac61fd/polars_runtime_32-1.40.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b748ef652270cc49e9e69f99a035e0eb4d5f856d42bcd6ac4d9d80a40142aa1e", size = 52098755, upload-time = "2026-04-22T19:14:28.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/3e/48599a38009ca60ff82a6f38c8a621ce3c0286aa7397c7d79e741bd9060e/polars_runtime_32-1.40.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:d249b3743e05986060cec0a7aaa542d020df6c6b876e556023a310efd581f9be", size = 46367542, upload-time = "2026-04-22T19:14:32.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e9/384bc069367a1a36ee31c13782c178dbd039b2b873b772d4a0fc23a2373d/polars_runtime_32-1.40.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5987b30e7aa1059d069498496e8dda35afd592b0ac3d46ed87e3ff8df1ad652c", size = 50252104, upload-time = "2026-04-22T19:14:35.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/ef/7d57ceb0651af74194e97ed6583e148d352f03d696090221b8059cdfc90b/polars_runtime_32-1.40.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d7f42a8b3f16fc66002cc0f6516f7dd7653396886ae0ed362ab95c0b3408b59", size = 56250788, upload-time = "2026-04-22T19:14:39.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/0f/e4b3ffc748827a14a474ec9c42e45c066050e440fec57e914091d9adda75/polars_runtime_32-1.40.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e5f7becc237a7ec9d9a10878dc8e54b73bbf4e2d94a2991c37d7a0b38590d8f9", size = 50432590, upload-time = "2026-04-22T19:14:43.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/0b/b8d95fbed869fa4caabe9c400e4210374913b376e925e96fdcfa9be6416b/polars_runtime_32-1.40.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:992d14cf191dde043d36fbdbc98a65e43fbc7e9a5024cecd45f838ac4988c1ee", size = 54155564, upload-time = "2026-04-22T19:14:47.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/d9/d091d8fb5cbed5e9536adfed955c4c89987a4cc3b8e73ae4532402b91c74/polars_runtime_32-1.40.1-cp310-abi3-win_amd64.whl", hash = "sha256:f78bb2abd00101cbb23cc0cb068f7e36e081057a15d2ec2dde3dda280709f030", size = 51829755, upload-time = "2026-04-22T19:14:50.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/ad/b33c3022a394f3eb55c3310597cec615412a8a33880055eee191d154a628/polars_runtime_32-1.40.1-cp310-abi3-win_arm64.whl", hash = "sha256:b5cbfaf6b085b420b4bfcbe24e8f665076d1cccfdb80c0484c02a023ce205537", size = 45822104, upload-time = "2026-04-22T19:14:54.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/e7/9d1630d666eca6a67e2096c0ed2c0e18f1355fe440043fd0830de1b71ab6/polars_runtime_32-1.41.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:766b60c74550382731b604ed62a385a8403b341bf18282d3fd2f746fa3c4cafd", size = 52163350, upload-time = "2026-05-22T20:19:34.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/b3/01538d51cd2790729ae13c23db44bc787bdfe20867faeb1087afc390c53b/polars_runtime_32-1.41.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:bee0d294daca79cedd5749e1bf3373c2d4107eb849fe544a60df6c08abc972ce", size = 46474331, upload-time = "2026-05-22T20:19:37.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/29/efa82e1b3e6711f254df3793f3d3fd99f26ef1bcaffa6533266fa6522de4/polars_runtime_32-1.41.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08b3f915468bf00d327b4a1236935a4ec3174dcb163785fcd98185ad1319a503", size = 50358997, upload-time = "2026-05-22T20:19:42.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/18/5a04b06b773047cbf43912ab802eef3c1d50ef7e66d51a41b16726f9bc62/polars_runtime_32-1.41.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72269f768c57229190dba0cb5abb8a1b228e96cc6331273a77a7957576885bd", size = 56332032, upload-time = "2026-05-22T20:19:45.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/d5/d728ce7a39ea925555db7d2c9f7b5df3ca17568e483db6501783f653e0b9/polars_runtime_32-1.41.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1532c7560a5c0fd06943080ee42aade721f186becdfbb1baafa622e3199c3b62", size = 50529017, upload-time = "2026-05-22T20:19:49.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/92/6b2092dfed4278f636499217767204fce19ed72695690e2c4eba99e2892e/polars_runtime_32-1.41.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:43368d8a754ee274f1e1099a7c09aae59cd69d22b8a6f83c0f782a00cd3a6662", size = 54244707, upload-time = "2026-05-22T20:19:52.852Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/6e/46b43be0f5becbef65843f438de3950ac6f8d0fa0008d7de0025eed00097/polars_runtime_32-1.41.0-cp310-abi3-win_amd64.whl", hash = "sha256:a9bd6095ecadc6799d166b9e8f7183a7ca8ba0a5aef8a426ec41df8ed8b09df7", size = 51918379, upload-time = "2026-05-22T20:19:55.832Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/da/30f15f0c3959b70e7a6583eccd37140a30b5c643ca374792d150b3a357df/polars_runtime_32-1.41.0-cp310-abi3-win_arm64.whl", hash = "sha256:ed922400f0eb393345fd7b6874b150eb943af2b816297a3dde03735cb5f3de08", size = 45921961, upload-time = "2026-05-22T20:19:59.525Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -903,11 +982,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pyclean"
|
||||
version = "3.6.0"
|
||||
version = "3.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/0d/7690e519b66101c41c114e2ddbf144f9b4ff6ea938fb199c09ed958f3970/pyclean-3.6.0.tar.gz", hash = "sha256:a46c179be187999d50d2d3362b2c94856e60f77de32ca7ec2db4eeb16bc39199", size = 27000, upload-time = "2026-03-30T17:34:30.509Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/cc/1039d7423a4fe961a36c41c985cf7c1a84270de922d4cc82e16494c7a8b5/pyclean-3.7.0.tar.gz", hash = "sha256:f4605562684fe721d79cfb92842d9d993d1aa7e9a1d13f316e45b29d02d9946b", size = 27580, upload-time = "2026-05-23T01:44:06.561Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/35/2a/1797004bb000db555bd45e38550b3ca10699f87e22a78e6753882e0ac2cf/pyclean-3.6.0-py3-none-any.whl", hash = "sha256:3e5f9c83f2472df5bdafa78ed533bd38690756e0a7b3b6f11c8b29c9affc728b", size = 27750, upload-time = "2026-03-30T17:34:29.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/5d/38264b704ed3dd35c765dda435118cf9a249048bd638fbd46d3fd51a9146/pyclean-3.7.0-py3-none-any.whl", hash = "sha256:76c1688c1a976889d526c1a2623107f4bc746ce26538f184f041422d91653858", size = 28374, upload-time = "2026-05-23T01:44:05.483Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -936,7 +1015,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pydantic-ai-slim"
|
||||
version = "1.99.0"
|
||||
version = "1.102.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "genai-prices" },
|
||||
|
|
@ -947,9 +1026,9 @@ dependencies = [
|
|||
{ name = "pydantic-graph" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/77/c6/ed4999450eb2d5106201eb378e5d1763f8af7bec445481bc14f4b1635ef0/pydantic_ai_slim-1.99.0.tar.gz", hash = "sha256:51435f81620d9bc7c2e0a124c19452db730660445810422669b2ba6183bce68b", size = 721667, upload-time = "2026-05-20T01:32:26.66Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/3e/14980440e8f0532535e1fbe936fec5f8d8e7bc6cafa81f6f3c51b1884fe5/pydantic_ai_slim-1.102.0.tar.gz", hash = "sha256:0b8f2b70fa2b40efcbd09d341a346934fc4e46622ae281f858c6bfd3d0d3152b", size = 739988, upload-time = "2026-05-23T01:14:32.808Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/9b/d1860e08f11600d35646ffce888a92d779803ac44f5bbe7229710efb0f95/pydantic_ai_slim-1.99.0-py3-none-any.whl", hash = "sha256:120964b74089b65088dc7ad5377bddaecfef3ce4da302d888fa668f2677d5cd7", size = 895702, upload-time = "2026-05-20T01:32:17.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/2e/089df86adaf904dd97a1b139d29fe728af0e41430d747f5b6315df3b0c1e/pydantic_ai_slim-1.102.0-py3-none-any.whl", hash = "sha256:f9fa9c3fb58a76f85522f78d1037d201b424de46d532263ed780b3730060449f", size = 919311, upload-time = "2026-05-23T01:14:23.464Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -1016,7 +1095,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pydantic-graph"
|
||||
version = "1.99.0"
|
||||
version = "1.102.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
|
|
@ -1024,9 +1103,9 @@ dependencies = [
|
|||
{ name = "pydantic" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/3d/b8481e0326a261a5c1bc1aa49d3218361a06f97bb133ddc891e90f2b6eb5/pydantic_graph-1.99.0.tar.gz", hash = "sha256:b9d7d56bd4fab1fc0bc881ae77d6c9be321cee85b428e7da7fd3ade0bc8010fe", size = 62552, upload-time = "2026-05-20T01:32:29.321Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/37/4265a1a63eddf35a5aa621c9b2355525bdeae3eb59c3954b165fbfe31404/pydantic_graph-1.102.0.tar.gz", hash = "sha256:e285bd7115e4e92676eaf0a5e7e6faa64cda8c4819f67923a118c50666b909ab", size = 62584, upload-time = "2026-05-23T01:14:36.056Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2e/49d51eba4698d6fe41c48fabf7f7f6bd3e3a4f3a0d29487e5ed742aba6cf/pydantic_graph-1.99.0-py3-none-any.whl", hash = "sha256:d40baf3effbe1610017234b09f873cfe956979fb4ec92e57d2705345e2943b33", size = 80092, upload-time = "2026-05-20T01:32:21.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/49/5597c52d50114440047dd4ce4f6505e32ee336f43267639907d1a17648ee/pydantic_graph-1.102.0-py3-none-any.whl", hash = "sha256:b1a28314adc4abca4db02cf095d064782ec5712e0847ce7a6b79a3c84bf1fc01", size = 80100, upload-time = "2026-05-23T01:14:27.583Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1178,6 +1257,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.29"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/fe/70bd71a6738b09a0bdf6480ca6436b167469ca4578b2a0efbe390b4b0e70/python_multipart-0.0.29.tar.gz", hash = "sha256:643e93849196645e2dbdd81a0f8829a23123ad7f797a84a364c6fb3563f18904", size = 45678, upload-time = "2026-05-17T17:29:47.654Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/cb/769cfc37177252872a45a71f3fbdde9d51b471a3f3c14bfe95dde3407386/python_multipart-0.0.29-py3-none-any.whl", hash = "sha256:2ddcc971cef266225f54f552d8fa10bcfbb1f14446caec199060daac59ff2d69", size = 29640, upload-time = "2026-05-17T17:29:45.69Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.3"
|
||||
|
|
@ -1447,27 +1535,49 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.13"
|
||||
version = "0.15.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustworkx"
|
||||
version = "0.17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/b0/66d96f02120f79eeed86b5c5be04029b6821155f31ed4907a4e9f1460671/rustworkx-0.17.1.tar.gz", hash = "sha256:59ea01b4e603daffa4e8827316c1641eef18ae9032f0b1b14aa0181687e3108e", size = 399407, upload-time = "2025-09-15T16:29:46.429Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/24/8972ed631fa05fdec05a7bb7f1fc0f8e78ee761ab37e8a93d1ed396ba060/rustworkx-0.17.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c08fb8db041db052da404839b064ebfb47dcce04ba9a3e2eb79d0c65ab011da4", size = 2257491, upload-time = "2025-08-13T01:43:31.466Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/ae/7b6bbae5e0487ee42072dc6a46edf5db9731a0701ed648db22121fb7490c/rustworkx-0.17.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:4ef8e327dadf6500edd76fedb83f6d888b9266c58bcdbffd5a40c33835c9dd26", size = 2040175, upload-time = "2025-08-13T01:43:33.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/ea/c17fb9428c8f0dcc605596f9561627a5b9ef629d356204ee5088cfcf52c6/rustworkx-0.17.1-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b809e0aa2927c68574b196f993233e269980918101b0dd235289c4f3ddb2115", size = 2324771, upload-time = "2025-08-13T01:43:35.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/40/ec8b3b8b0f8c0b768690c454b8dcc2781b4f2c767f9f1215539c7909e35b/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7e82c46a92fb0fd478b7372e15ca524c287485fdecaed37b8bb68f4df2720f2", size = 2068584, upload-time = "2025-08-13T01:43:37.261Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/22/713b900d320d06ce8677e71bba0ec5df0037f1d83270bff5db3b271c10d7/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42170075d8a7319e89ff63062c2f1d1116ced37b6f044f3bf36d10b60a107aa4", size = 2380949, upload-time = "2025-08-13T01:52:17.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/4b/54be84b3b41a19caf0718a2b6bb280dde98c8626c809c969f16aad17458f/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65cba97fa95470239e2d65eb4db1613f78e4396af9f790ff771b0e5476bfd887", size = 2562069, upload-time = "2025-08-13T02:09:27.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/5b/281bb21d091ab4e36cf377088366d55d0875fa2347b3189c580ec62b44c7/rustworkx-0.17.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246cc252053f89e36209535b9c58755960197e6ae08d48d3973760141c62ac95", size = 2221186, upload-time = "2025-08-13T01:43:38.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/2d/30a941a21b81e9db50c4c3ef8a64c5ee1c8eea3a90506ca0326ce39d021f/rustworkx-0.17.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c10d25e9f0e87d6a273d1ea390b636b4fb3fede2094bf0cb3fe565d696a91b48", size = 2123510, upload-time = "2025-08-13T01:43:40.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/ef/c9199e4b6336ee5a9f1979c11b5779c5cf9ab6f8386e0b9a96c8ffba7009/rustworkx-0.17.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:48784a673cf8d04f3cd246fa6b53fd1ccc4d83304503463bd561c153517bccc1", size = 2302783, upload-time = "2025-08-13T01:43:42.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/3d/a49ab633e99fca4ccbb9c9f4bd41904186c175ebc25c530435529f71c480/rustworkx-0.17.1-cp39-abi3-win32.whl", hash = "sha256:5dbc567833ff0a8ad4580a4fe4bde92c186d36b4c45fca755fb1792e4fafe9b5", size = 1931541, upload-time = "2025-08-13T01:43:43.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/ec/cee878c1879b91ab8dc7d564535d011307839a2fea79d2a650413edf53be/rustworkx-0.17.1-cp39-abi3-win_amd64.whl", hash = "sha256:d0a48fb62adabd549f9f02927c3a159b51bf654c7388a12fc16d45452d5703ea", size = 2055049, upload-time = "2025-08-13T01:43:44.926Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1481,11 +1591,17 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "smithy"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "numpy" },
|
||||
{ name = "polars" },
|
||||
{ name = "rustworkx" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
cli = [
|
||||
{ name = "click" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
|
||||
|
|
@ -1505,10 +1621,13 @@ dev = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.4.0" },
|
||||
{ name = "click", marker = "extra == 'cli'", specifier = ">=8.4.0" },
|
||||
{ name = "numpy", specifier = ">=2.4.6" },
|
||||
{ name = "polars", specifier = ">=1.40.1" },
|
||||
{ name = "rich", specifier = ">=15.0.0" },
|
||||
{ name = "rich", marker = "extra == 'cli'", specifier = ">=15.0.0" },
|
||||
{ name = "rustworkx", specifier = ">=0.17.1" },
|
||||
]
|
||||
provides-extras = ["cli"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
|
|
@ -1565,14 +1684,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/66/4d20cdf39a8d6a51e663b7038e3b828ff211d3891a43a713fe7e4643f3a8/starlette-1.1.0.tar.gz", hash = "sha256:e83c7fe0ddecd8719c5b840080325aec0260acec86e9832899e377b91d65e90f", size = 2660060, upload-time = "2026-05-23T16:55:41.376Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/79/920b8e0a8b20f793e8d64855095cb8febabf6175b8550b6f7a547d813891/starlette-1.1.0-py3-none-any.whl", hash = "sha256:7f0dfd38e428aad5cb6f9f667f0ca1d2d8ca3f3385dccac8305f79ec98458382", size = 72899, upload-time = "2026-05-23T16:55:39.201Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1677,41 +1796,41 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "uv"
|
||||
version = "0.11.15"
|
||||
version = "0.11.16"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/da/34/609d5d01ba21dc8f0974610ca7802fbb2c946a0c38665cfe5c5aeddbefb5/uv-0.11.15.tar.gz", hash = "sha256:755f959ec6a2fd8ccb6ee76ad90ab759d2eb1f4797444078645dd1ee4bca92d6", size = 4159545, upload-time = "2026-05-18T19:57:48.133Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/99/025154611a4bd97a23851574c15d73bb71ada09d35f092d6972f9ac87f70/uv-0.11.16.tar.gz", hash = "sha256:4b435fcb0af8f34833dcc1903a8a223856437efd0d515c2160a2871def221238", size = 4177038, upload-time = "2026-05-21T22:10:01.009Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/7c/dcc230c5911884d8848145dabcac8fb95a5ed6f9fe1c57fae8242618f28a/uv-0.11.15-py3-none-linux_armv6l.whl", hash = "sha256:83b04ab49514a0a761ffedb36a748ee81f87746671e72088e5f32c9585e5f1a9", size = 23110183, upload-time = "2026-05-18T19:57:23.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f3/efd4e044b60eb9c3c12ee386be098d56c335538ccec7caa49349cfba9344/uv-0.11.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cae61f737be075b90be9e3f07d961072aed7019f4c9b8ed5c5d41c4d6cade3", size = 22637941, upload-time = "2026-05-18T19:57:26.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/b8/48627f895a1569e576822e0a8416aa4797eb4a4551de21a4ad97b9b5819d/uv-0.11.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9accae33619a9166e5c48531deb455d672cfb89f9357a00975e669c76b0bd49f", size = 21258803, upload-time = "2026-05-18T19:57:05.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/50/4bc8a148274feabee2d9c9f1fa15009e10c0228dfe57981ee3ea2ef1d481/uv-0.11.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c0cf52cd6d50bb9e05e2d968f45f80761107e4cbc8d4a26d9758f9d8274aaec1", size = 23066178, upload-time = "2026-05-18T19:57:33.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/56/139fc3bec9a8b0a25bfe2196123adb9f16124da437bf4fbcf0d21cfcafb2/uv-0.11.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:49dc6ed70bff00937384f96cdc4b1a4742d18e5504ec2c4a1214dba2dee5687a", size = 22705332, upload-time = "2026-05-18T19:57:36.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b0/b18b3dd204f8c213236a1ebd148e009861637129a8cce34df0e9aa22ed40/uv-0.11.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adb9a89352539fdd8f7cd5f9966cf9f94fc5b98e0ccdf5003a04123dc6423bec", size = 22707534, upload-time = "2026-05-18T19:58:04.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/36/3ca09f95572df99d361b49c96b1297149e96e120d8d1ecf074095a4b6da4/uv-0.11.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40ff67e3f8e8a7533781a2e892a534975a93acb83ea35460e64e7b2bf2111774", size = 24096607, upload-time = "2026-05-18T19:58:11.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/be/3bdee21a296bbf5336a526e3613d0e7d4538dacc39c62d7fcba55d15f6b0/uv-0.11.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6463a299ed7e6b5a800ed6f108af8e1588352629424133ddef7572b0e1e1118", size = 25082562, upload-time = "2026-05-18T19:57:40.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/73/f371f3689ffe741066468d001d85f739fc4b5574de83b639ef19b5e8a7f4/uv-0.11.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68c1e62d4b78578b90b833553286b65d6a7e327537716441068583ba652ec4f5", size = 24253391, upload-time = "2026-05-18T19:57:18.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/16/fe392d618af6b00c064b3e718d585dcf791546a77c5123a5bec07ce53a0a/uv-0.11.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98edf1bdaf82447014852051d93e3ee95012509c567bf057fd117e6bdbd9a807", size = 24415871, upload-time = "2026-05-18T19:58:19.651Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/24/2e92a052fb6334fcd746d1c7cb57847c204b118c84f5da53c0f9e129f7b7/uv-0.11.15-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:be8f76d25bcf4c92bb384240ac1bf9aa7f51063d0bdeca4c9cf0ec3ed8b145e0", size = 23159007, upload-time = "2026-05-18T19:57:10.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/2e/6923d0658d164bb2c435ed1868aa2d49b3074594679917a001ff92dc95bb/uv-0.11.15-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:f9f4fbbf4fe485522054f3c7496c6e8e932d6436e4200ff3daf718db0b7c7bd5", size = 23769385, upload-time = "2026-05-18T19:58:15.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/99/7e34cd949e57360814e8064cc9fb7104df445d0f6a663504e5f7473480aa/uv-0.11.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0ed920e896b2fd13a35031707e307e42fbb2681458b967440a17272d86d49137", size = 23860973, upload-time = "2026-05-18T19:57:55.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/98/8fe1f5f9d816e94569a0298dd8e0936801097625fa1952162951f0d628b6/uv-0.11.15-py3-none-musllinux_1_1_i686.whl", hash = "sha256:41d907611f3e6a13262807fd7f0a17849f76285ca80f536f6b3943732bdc6656", size = 23431392, upload-time = "2026-05-18T19:57:59.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/6b/76a1ce2fa860026913a5941700cdc7d715fce9c3277a3fa3489cf2523ca0/uv-0.11.15-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:e3b68f8bf1a4568710f77e5bda9182ce7682811d89a8e7468c22460e032b234d", size = 24519478, upload-time = "2026-05-18T19:57:51.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/60/1d58e8a05718cb50494763115710b73846cacb651fd735d285233fd72c59/uv-0.11.15-py3-none-win32.whl", hash = "sha256:8e2da3076761086a5b76869c3f38ef0509c836046ef41ddd19485dfd7271dca9", size = 22020178, upload-time = "2026-05-18T19:58:07.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/53/40fcefcb348af660488597ed3c01363df7344e60611f8883750dc596f5c6/uv-0.11.15-py3-none-win_amd64.whl", hash = "sha256:cc3915ab291a1ecaf31de05f5d8bd70d09c66fe9911a53f70d9efa62ff0dbd8a", size = 24668779, upload-time = "2026-05-18T19:57:44.894Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/7d/fa3a9960c95af9bbe2a629048760d0b9b4fead8ccd4f2235af747ec7cdf0/uv-0.11.15-py3-none-win_arm64.whl", hash = "sha256:4f39426a13dee24897aed60c4b98058c66f18bd983885ac5f4a54a04b24fbddf", size = 23198178, upload-time = "2026-05-18T19:57:14.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/e3/8b8cfc802bc476c67e31a39725538193265cf3a19585b4a60c232659f919/uv-0.11.16-py3-none-linux_armv6l.whl", hash = "sha256:c9e9d9cb73ee8cd2ad696dbf1bc3232abaac363270557684b6b85a2bdb8eb276", size = 23508087, upload-time = "2026-05-21T22:10:06.227Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/78/d5ca91c636ac88e902b6b3ff31ad32d2d02663232d844aff871467a323d2/uv-0.11.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:01172238a75e42a5a55d12555cd9ec98bee24249f3645b98a4b32eb5f1ff5e43", size = 23028989, upload-time = "2026-05-21T22:09:50.127Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/26/c84580dfec5a87c36fb1218eac17c5194fa3e58e2a9232cf085d69eb6bed/uv-0.11.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c75f9b5bac49b97131973910c220feac60fe47b10a333941b237ff0ae4b36721", size = 21572023, upload-time = "2026-05-21T22:09:58.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/68/ba2bdc64fea96ef8c9796a991f244541b65bb9d31c661b322cc724857a4e/uv-0.11.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a801484f4507b6c2133e557350f3143b61b8f8b61dddb01ff7b84a74cdfab1fb", size = 23289936, upload-time = "2026-05-21T22:10:15.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/81/74922f693d5804a77d009338ca8dc709eff871fb60d9f2c263dede8d77d1/uv-0.11.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:eb538069e768b042cf870be700a210518ce628e36d99d9a83b85acaf484d7f6a", size = 23020906, upload-time = "2026-05-21T22:10:24.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/81/cda8886f5df4dd28854a9b97bcc3ee6a7d1b5b5b23aaaccfbf1ed3e5e2bf/uv-0.11.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7cdb23457a4d1bc76bf1016638ea1d1ada0e8e032f656168e933d4d17c47e72", size = 23004220, upload-time = "2026-05-21T22:10:32.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/7c/65837e07de23f0a40ab860bc6601f7c022d4bcf4b97ca79b6c35a2e72e65/uv-0.11.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:451327388d59ac3041cbda474296f3ceeafac5b1f645476198e7b95f504fcfd5", size = 24319651, upload-time = "2026-05-21T22:10:21.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/70/9d364542bf118433b60ed71422e47d2c8c470aca7d3aef0df9449a5f726a/uv-0.11.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7992b8276149b3ffaf35ce9434702d3e16bae6ec393e99df209b870a7e19eb0", size = 25359517, upload-time = "2026-05-21T22:09:46.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/b4/650896e8cff5a3289cee860c41fd9876da83ca628c5871f9a61d5fc75c72/uv-0.11.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a8db9b3314d900e7a240105afce43f806c9e04c59ea10a40bdbdca84c6d0c5", size = 24563421, upload-time = "2026-05-21T22:10:35.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/7d/184711a8c02466e1486d57efdc9394ce09cbf43ee2c5794da70bd25db3fb/uv-0.11.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b10086165189c39c53142a0e2f34e0b8889ef681886f589ed17be45a1a774c7", size = 24676607, upload-time = "2026-05-21T22:10:39.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/3f/5b338df6505f77f73c20eae38cb29f57d14dba56dac835386e3dc6e2a5d6/uv-0.11.16-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:cfe1f06fb8f135a735a961065d5ee90f99cccf41749fb1f964edb5b3c3dae19b", size = 23401615, upload-time = "2026-05-21T22:10:30.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/f9/54bbcbc77443dc76468f09a49cc9f4f92ca49b4159a011c6010d223de4ea/uv-0.11.16-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:2454f80d8b548fb2e246151578809b14ad4395b3f357d738bae1af11918e91af", size = 24104468, upload-time = "2026-05-21T22:09:53.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/0a/b5f105514fddea5110fe3947cd18a9f199ff93dbad78e5e5a08e1b5d0ea2/uv-0.11.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:4249d57a563165d368050680deeb722f9c0053a0dbf3244b11cca3e6d85a3c7d", size = 24164861, upload-time = "2026-05-21T22:10:09.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/01/15d4ca2be7257862b077a9077ac31ce81c419f35ef7994e76356a317716b/uv-0.11.16-py3-none-musllinux_1_1_i686.whl", hash = "sha256:374c30126483ce95675c5de49e54c2454ddedb01c17b8321417fe4eb9da83406", size = 23644919, upload-time = "2026-05-21T22:10:03.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/bf/9de3e262e6ff93aec2e0a4c238857293fd2c616dd79f25bb440f126bf32c/uv-0.11.16-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:746edfc9d1d8cd03dd58739989f634d3580648048d09f81a9c68da74c4eb9d62", size = 24973746, upload-time = "2026-05-21T22:10:18.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/7d/f4126dce104f1b5d0b451ce3ca41c4db69b963c2e78c3465fcda6440de31/uv-0.11.16-py3-none-win32.whl", hash = "sha256:50299b20aab2d28c05ff27d781ce2af3f5af2102bc304dc07a4ad54b05e2af8a", size = 22400991, upload-time = "2026-05-21T22:10:27.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/38/99627cb995a03389b227ce4b12b08e770565d0aa7850cd0420973194a638/uv-0.11.16-py3-none-win_amd64.whl", hash = "sha256:e901aafa5007beffafe57bfa44e5e248d99fb5d97036a3718fd65cf9723c5cd3", size = 25067163, upload-time = "2026-05-21T22:10:12.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/68/3ed1c0bdfb4bec501e5cde73419b4f39c8a125ef905a85fc0f239f19eb9b/uv-0.11.16-py3-none-win_arm64.whl", hash = "sha256:d777cb29661cdfa7f90dae77406c85fb5b729bf8bc13941dc237958a1ea1ba00", size = 23502015, upload-time = "2026-05-21T22:09:56.014Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/b1/8e7077a8641086aea449e1b5752a570f1b5906c64e0a33cd6d93b63a066b/uvicorn-0.47.0.tar.gz", hash = "sha256:7c9a0ea1a9414106bbab7324609c162d8fa0cdcdcb703060987269d77c7bb533", size = 90582, upload-time = "2026-05-14T18:16:54.455Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/41/ac2dfdbc1f60c7af4f994c7a335cfa7040c01642b605d65f611cecc2a1e4/uvicorn-0.47.0-py3-none-any.whl", hash = "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", size = 71301, upload-time = "2026-05-14T18:16:51.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue