mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
Turn the fuzz-parser
script into a properly packaged Python project (#14606)
## Summary This PR gets rid of the `requirements.in` and `requirements.txt` files in the `scripts/fuzz-parser` directory, and replaces them with `pyproject.toml` and `uv.lock` files. The script is renamed from `fuzz-parser` to `py-fuzzer` (since it can now also be used to fuzz red-knot as well as the parser, following https://github.com/astral-sh/ruff/pull/14566), and moved from the `scripts/` directory to the `python/` directory, since it's now a (uv)-pip-installable project in its own right. I've been resisting this for a while, because conceptually this script just doesn't feel "complicated" enough to me for it to be a full-blown package. However, I think it's time to do this. Making it a proper package has several advantages: - It means we can run it from the project root using `uv run` without having to activate a virtual environment and ensure that all required dependencies are installed into that environment - Using a `pyproject.toml` file means that we can express that the project requires Python 3.12+ to run properly; this wasn't possible before - I've been running mypy on the project locally when I've been working on it or reviewing other people's PRs; now I can put the mypy config for the project in the `pyproject.toml` file ## Test Plan I manually tested that all the commands detailed in `python/py-fuzzer/README.md` work for me locally. --------- Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
This commit is contained in:
parent
c84c690f1e
commit
e0f3eaf1dd
10 changed files with 379 additions and 83 deletions
8
python/py-fuzzer/README.md
Normal file
8
python/py-fuzzer/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# py-fuzzer
|
||||
|
||||
A fuzzer script to run Ruff executables on randomly generated
|
||||
(but syntactically valid) Python source-code files.
|
||||
|
||||
Run `uv run --no-project --with ./python/py-fuzzer fuzz -h` from the repository root
|
||||
for more information and example invocations
|
||||
(requires [`uv`](https://github.com/astral-sh/uv) to be installed).
|
406
python/py-fuzzer/fuzz.py
Normal file
406
python/py-fuzzer/fuzz.py
Normal file
|
@ -0,0 +1,406 @@
|
|||
"""
|
||||
Run a Ruff executable on randomly generated (but syntactically valid)
|
||||
Python source-code files.
|
||||
|
||||
This script can be installed into a virtual environment using
|
||||
`uv pip install -e ./python/py-fuzzer` from the Ruff repository root,
|
||||
or can be run using `uv run --no-project --with ./python/py-fuzzer`
|
||||
(in which case the virtual environment does not need to be activated).
|
||||
|
||||
Example invocations of the script using `uv`:
|
||||
- Run the fuzzer on Ruff's parser using seeds 0, 1, 2, 78 and 93 to generate the code:
|
||||
`uv run --no-project --with ./python/py-fuzzer fuzz --bin ruff 0-2 78 93`
|
||||
- Run the fuzzer concurrently using seeds in range 0-10 inclusive,
|
||||
but only reporting bugs that are new on your branch:
|
||||
`uv run --no-project --with ./python/py-fuzzer fuzz --bin ruff 0-10 --new-bugs-only`
|
||||
- Run the fuzzer concurrently on 10,000 different Python source-code files,
|
||||
using a random selection of seeds, and only print a summary at the end
|
||||
(the `shuf` command is Unix-specific):
|
||||
`uv run --no-project --with ./python/py-fuzzer fuzz --bin ruff $(shuf -i 0-1000000 -n 10000) --quiet
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import concurrent.futures
|
||||
import enum
|
||||
import subprocess
|
||||
import tempfile
|
||||
from dataclasses import KW_ONLY, dataclass
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import NewType, NoReturn, assert_never
|
||||
|
||||
from pysource_codegen import generate as generate_random_code
|
||||
from pysource_minimize import CouldNotMinimize, minimize as minimize_repro
|
||||
from rich_argparse import RawDescriptionRichHelpFormatter
|
||||
from termcolor import colored
|
||||
|
||||
MinimizedSourceCode = NewType("MinimizedSourceCode", str)
|
||||
Seed = NewType("Seed", int)
|
||||
ExitCode = NewType("ExitCode", int)
|
||||
|
||||
|
||||
def redknot_contains_bug(code: str, *, red_knot_executable: Path) -> bool:
|
||||
"""Return `True` if the code triggers a panic in type-checking code."""
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
Path(tempdir, "pyproject.toml").write_text('[project]\n\tname = "fuzz-input"')
|
||||
Path(tempdir, "input.py").write_text(code)
|
||||
completed_process = subprocess.run(
|
||||
[red_knot_executable, "--current-directory", tempdir],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return completed_process.returncode != 0 and completed_process.returncode != 1
|
||||
|
||||
|
||||
def ruff_contains_bug(code: str, *, ruff_executable: Path) -> bool:
|
||||
"""Return `True` if the code triggers a parser error."""
|
||||
completed_process = subprocess.run(
|
||||
[ruff_executable, "check", "--config", "lint.select=[]", "--no-cache", "-"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
input=code,
|
||||
)
|
||||
return completed_process.returncode != 0
|
||||
|
||||
|
||||
def contains_bug(code: str, *, executable: Executable, executable_path: Path) -> bool:
|
||||
"""Return `True` if the code triggers an error."""
|
||||
match executable:
|
||||
case Executable.RUFF:
|
||||
return ruff_contains_bug(code, ruff_executable=executable_path)
|
||||
case Executable.RED_KNOT:
|
||||
return redknot_contains_bug(code, red_knot_executable=executable_path)
|
||||
case _ as unreachable:
|
||||
assert_never(unreachable)
|
||||
|
||||
|
||||
def contains_new_bug(
|
||||
code: str,
|
||||
*,
|
||||
executable: Executable,
|
||||
test_executable_path: Path,
|
||||
baseline_executable_path: Path,
|
||||
) -> bool:
|
||||
"""Return `True` if the code triggers a *new* parser error.
|
||||
|
||||
A "new" parser error is one that exists with `test_executable`,
|
||||
but did not exist with `baseline_executable`.
|
||||
"""
|
||||
return contains_bug(
|
||||
code, executable=executable, executable_path=test_executable_path
|
||||
) and not contains_bug(
|
||||
code, executable=executable, executable_path=baseline_executable_path
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class FuzzResult:
|
||||
# The seed used to generate the random Python file.
|
||||
# The same seed always generates the same file.
|
||||
seed: Seed
|
||||
# If we found a bug, this will be the minimum Python code
|
||||
# required to trigger the bug. If not, it will be `None`.
|
||||
maybe_bug: MinimizedSourceCode | None
|
||||
# The executable we're testing
|
||||
executable: Executable
|
||||
|
||||
def print_description(self, index: int, num_seeds: int) -> None:
|
||||
"""Describe the results of fuzzing the parser with this seed."""
|
||||
progress = f"[{index}/{num_seeds}]"
|
||||
msg = (
|
||||
colored(f"Ran fuzzer on seed {self.seed}", "red")
|
||||
if self.maybe_bug
|
||||
else colored(f"Ran fuzzer successfully on seed {self.seed}", "green")
|
||||
)
|
||||
print(f"{msg:<60} {progress:>15}", flush=True)
|
||||
|
||||
if self.maybe_bug:
|
||||
match self.executable:
|
||||
case Executable.RUFF:
|
||||
panic_message = "The following code triggers a parser bug:"
|
||||
case Executable.RED_KNOT:
|
||||
panic_message = "The following code triggers a red-knot panic:"
|
||||
case _ as unreachable:
|
||||
assert_never(unreachable)
|
||||
|
||||
print(colored(panic_message, "red"))
|
||||
print()
|
||||
print(self.maybe_bug)
|
||||
print(flush=True)
|
||||
|
||||
|
||||
def fuzz_code(seed: Seed, args: ResolvedCliArgs) -> FuzzResult:
|
||||
"""Return a `FuzzResult` instance describing the fuzzing result from this seed."""
|
||||
code = generate_random_code(seed)
|
||||
has_bug = (
|
||||
contains_new_bug(
|
||||
code,
|
||||
executable=args.executable,
|
||||
test_executable_path=args.test_executable_path,
|
||||
baseline_executable_path=args.baseline_executable_path,
|
||||
)
|
||||
if args.baseline_executable_path is not None
|
||||
else contains_bug(
|
||||
code, executable=args.executable, executable_path=args.test_executable_path
|
||||
)
|
||||
)
|
||||
if has_bug:
|
||||
callback = partial(
|
||||
contains_bug,
|
||||
executable=args.executable,
|
||||
executable_path=args.test_executable_path,
|
||||
)
|
||||
try:
|
||||
maybe_bug = MinimizedSourceCode(minimize_repro(code, callback))
|
||||
except CouldNotMinimize as e:
|
||||
# This is to double-check that there isn't a bug in
|
||||
# `pysource-minimize`/`pysource-codegen`.
|
||||
# `pysource-minimize` *should* never produce code that's invalid syntax.
|
||||
try:
|
||||
ast.parse(code)
|
||||
except SyntaxError:
|
||||
raise e from None
|
||||
else:
|
||||
maybe_bug = MinimizedSourceCode(code)
|
||||
|
||||
else:
|
||||
maybe_bug = None
|
||||
return FuzzResult(seed, maybe_bug, args.executable)
|
||||
|
||||
|
||||
def run_fuzzer_concurrently(args: ResolvedCliArgs) -> list[FuzzResult]:
|
||||
num_seeds = len(args.seeds)
|
||||
print(
|
||||
f"Concurrently running the fuzzer on "
|
||||
f"{num_seeds} randomly generated source-code files..."
|
||||
)
|
||||
bugs: list[FuzzResult] = []
|
||||
with concurrent.futures.ProcessPoolExecutor() as executor:
|
||||
fuzz_result_futures = [
|
||||
executor.submit(fuzz_code, seed, args) for seed in args.seeds
|
||||
]
|
||||
try:
|
||||
for i, future in enumerate(
|
||||
concurrent.futures.as_completed(fuzz_result_futures), start=1
|
||||
):
|
||||
fuzz_result = future.result()
|
||||
if not args.quiet:
|
||||
fuzz_result.print_description(i, num_seeds)
|
||||
if fuzz_result.maybe_bug:
|
||||
bugs.append(fuzz_result)
|
||||
except KeyboardInterrupt:
|
||||
print("\nShutting down the ProcessPoolExecutor due to KeyboardInterrupt...")
|
||||
print("(This might take a few seconds)")
|
||||
executor.shutdown(cancel_futures=True)
|
||||
raise
|
||||
return bugs
|
||||
|
||||
|
||||
def run_fuzzer_sequentially(args: ResolvedCliArgs) -> list[FuzzResult]:
|
||||
num_seeds = len(args.seeds)
|
||||
print(
|
||||
f"Sequentially running the fuzzer on "
|
||||
f"{num_seeds} randomly generated source-code files..."
|
||||
)
|
||||
bugs: list[FuzzResult] = []
|
||||
for i, seed in enumerate(args.seeds, start=1):
|
||||
fuzz_result = fuzz_code(seed, args)
|
||||
if not args.quiet:
|
||||
fuzz_result.print_description(i, num_seeds)
|
||||
if fuzz_result.maybe_bug:
|
||||
bugs.append(fuzz_result)
|
||||
return bugs
|
||||
|
||||
|
||||
def run_fuzzer(args: ResolvedCliArgs) -> ExitCode:
|
||||
if len(args.seeds) <= 5:
|
||||
bugs = run_fuzzer_sequentially(args)
|
||||
else:
|
||||
bugs = run_fuzzer_concurrently(args)
|
||||
noun_phrase = "New bugs" if args.baseline_executable_path is not None else "Bugs"
|
||||
if bugs:
|
||||
print(colored(f"{noun_phrase} found in the following seeds:", "red"))
|
||||
print(*sorted(bug.seed for bug in bugs))
|
||||
return ExitCode(1)
|
||||
else:
|
||||
print(colored(f"No {noun_phrase.lower()} found!", "green"))
|
||||
return ExitCode(0)
|
||||
|
||||
|
||||
def parse_seed_argument(arg: str) -> int | range:
|
||||
"""Helper for argument parsing"""
|
||||
if "-" in arg:
|
||||
start, end = map(int, arg.split("-"))
|
||||
if end <= start:
|
||||
raise argparse.ArgumentTypeError(
|
||||
f"Error when parsing seed argument {arg!r}: "
|
||||
f"range end must be > range start"
|
||||
)
|
||||
seed_range = range(start, end + 1)
|
||||
range_too_long = (
|
||||
f"Error when parsing seed argument {arg!r}: "
|
||||
f"maximum allowed range length is 1_000_000_000"
|
||||
)
|
||||
try:
|
||||
if len(seed_range) > 1_000_000_000:
|
||||
raise argparse.ArgumentTypeError(range_too_long)
|
||||
except OverflowError:
|
||||
raise argparse.ArgumentTypeError(range_too_long) from None
|
||||
return range(int(start), int(end) + 1)
|
||||
return int(arg)
|
||||
|
||||
|
||||
class Executable(enum.StrEnum):
|
||||
RUFF = "ruff"
|
||||
RED_KNOT = "red_knot"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ResolvedCliArgs:
|
||||
seeds: list[Seed]
|
||||
_: KW_ONLY
|
||||
executable: Executable
|
||||
test_executable_path: Path
|
||||
baseline_executable_path: Path | None
|
||||
quiet: bool
|
||||
|
||||
|
||||
def parse_args() -> ResolvedCliArgs:
|
||||
"""Parse command-line arguments"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=RawDescriptionRichHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
"seeds",
|
||||
type=parse_seed_argument,
|
||||
nargs="+",
|
||||
help="Either a single seed, or an inclusive range of seeds in the format `0-5`",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--only-new-bugs",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Only report bugs if they exist on the current branch, "
|
||||
"but *didn't* exist on the released version "
|
||||
"installed into the Python environment we're running in"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Print fewer things to the terminal while running the fuzzer",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--test-executable",
|
||||
help=(
|
||||
"Executable to test. "
|
||||
"Defaults to a fresh build of the currently checked-out branch."
|
||||
),
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--baseline-executable",
|
||||
help=(
|
||||
"Executable to compare results against. "
|
||||
"Defaults to whatever version is installed "
|
||||
"in the Python environment."
|
||||
),
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--bin",
|
||||
help="Which executable to test.",
|
||||
required=True,
|
||||
choices=[member.value for member in Executable],
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
executable = Executable(args.bin)
|
||||
|
||||
if args.baseline_executable:
|
||||
if not args.only_new_bugs:
|
||||
parser.error(
|
||||
"Specifying `--baseline-executable` has no effect "
|
||||
"unless `--only-new-bugs` is also specified"
|
||||
)
|
||||
try:
|
||||
subprocess.run(
|
||||
[args.baseline_executable, "--version"], check=True, capture_output=True
|
||||
)
|
||||
except FileNotFoundError:
|
||||
parser.error(
|
||||
f"Bad argument passed to `--baseline-executable`: "
|
||||
f"no such file or executable {args.baseline_executable!r}"
|
||||
)
|
||||
elif args.only_new_bugs:
|
||||
try:
|
||||
version_proc = subprocess.run(
|
||||
[executable, "--version"], text=True, capture_output=True, check=True
|
||||
)
|
||||
except FileNotFoundError:
|
||||
parser.error(
|
||||
"`--only-new-bugs` was specified without specifying a baseline "
|
||||
f"executable, and no released version of `{executable}` appears to be "
|
||||
"installed in your Python environment"
|
||||
)
|
||||
else:
|
||||
if not args.quiet:
|
||||
version = version_proc.stdout.strip().split(" ")[1]
|
||||
print(
|
||||
f"`--only-new-bugs` was specified without specifying a baseline "
|
||||
f"executable; falling back to using `{executable}=={version}` as "
|
||||
f"the baseline (the version of `{executable}` installed in your "
|
||||
f"current Python environment)"
|
||||
)
|
||||
|
||||
if not args.test_executable:
|
||||
print(
|
||||
"Running `cargo build --release` since no test executable was specified...",
|
||||
flush=True,
|
||||
)
|
||||
cmd: list[str] = [
|
||||
"cargo",
|
||||
"build",
|
||||
"--release",
|
||||
"--locked",
|
||||
"--color",
|
||||
"always",
|
||||
"--bin",
|
||||
executable,
|
||||
]
|
||||
try:
|
||||
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.stderr)
|
||||
raise
|
||||
args.test_executable = Path("target", "release", executable)
|
||||
assert args.test_executable.is_file()
|
||||
|
||||
seed_arguments: list[range | int] = args.seeds
|
||||
seen_seeds: set[int] = set()
|
||||
for arg in seed_arguments:
|
||||
if isinstance(arg, int):
|
||||
seen_seeds.add(arg)
|
||||
else:
|
||||
seen_seeds.update(arg)
|
||||
|
||||
return ResolvedCliArgs(
|
||||
sorted(map(Seed, seen_seeds)),
|
||||
quiet=args.quiet,
|
||||
executable=executable,
|
||||
test_executable_path=args.test_executable,
|
||||
baseline_executable_path=args.baseline_executable,
|
||||
)
|
||||
|
||||
|
||||
def main() -> NoReturn:
|
||||
args = parse_args()
|
||||
raise SystemExit(run_fuzzer(args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
79
python/py-fuzzer/pyproject.toml
Normal file
79
python/py-fuzzer/pyproject.toml
Normal file
|
@ -0,0 +1,79 @@
|
|||
[project]
|
||||
name = "py-fuzzer"
|
||||
version = "0.0.0"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"pysource-codegen>=0.6.0",
|
||||
"pysource-minimize>=0.7.0",
|
||||
"rich-argparse>=1.6.0",
|
||||
"ruff>=0.8.0",
|
||||
"termcolor>=2.5.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
fuzz = "fuzz:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["mypy", "ruff"]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["fuzz.py"]
|
||||
|
||||
[tool.mypy]
|
||||
files = "fuzz.py"
|
||||
pretty = true
|
||||
strict = true
|
||||
warn_unreachable = true
|
||||
local_partial_types = true
|
||||
enable_error_code = "ignore-without-code,redundant-expr,truthy-bool"
|
||||
|
||||
[tool.ruff]
|
||||
fix = true
|
||||
preview = true
|
||||
|
||||
[tool.ruff.format]
|
||||
docstring-code-format = true
|
||||
skip-magic-trailing-comma = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"ARG",
|
||||
"E",
|
||||
"F",
|
||||
"B",
|
||||
"B9",
|
||||
"C4",
|
||||
"SIM",
|
||||
"I",
|
||||
"UP",
|
||||
"PIE",
|
||||
"PGH",
|
||||
"PYI",
|
||||
"RUF",
|
||||
]
|
||||
ignore = [
|
||||
# only relevant if you run a script with `python -0`,
|
||||
"B011",
|
||||
# These are enforced by, or incompatible with, the ruff formatter:
|
||||
"E203",
|
||||
"E501",
|
||||
# Makes code slower and more verbose
|
||||
# https://github.com/astral-sh/ruff/issues/7871
|
||||
"UP038",
|
||||
]
|
||||
unfixable = [
|
||||
"F841", # unused variable. ruff keeps the call, but mostly it's best to get rid of it all
|
||||
"F601", # automatic fix might obscure issue
|
||||
"F602", # automatic fix might obscure issue
|
||||
"B018", # automatic fix might obscure issue
|
||||
"RUF017", # Ruff's fix is faster, but I prefer using itertools.chain_from_iterable
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
combine-as-imports = true
|
||||
split-on-trailing-comma = false
|
243
python/py-fuzzer/uv.lock
generated
Normal file
243
python/py-fuzzer/uv.lock
generated
Normal file
|
@ -0,0 +1,243 @@
|
|||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "astunparse"
|
||||
version = "1.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
{ name = "wheel" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", size = 18290 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "py-fuzzer"
|
||||
version = "0.0.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "pysource-codegen" },
|
||||
{ name = "pysource-minimize" },
|
||||
{ name = "rich-argparse" },
|
||||
{ name = "ruff" },
|
||||
{ name = "termcolor" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "pysource-codegen", specifier = ">=0.6.0" },
|
||||
{ name = "pysource-minimize", specifier = ">=0.7.0" },
|
||||
{ name = "rich-argparse", specifier = ">=1.6.0" },
|
||||
{ name = "ruff", specifier = ">=0.8.0" },
|
||||
{ name = "termcolor", specifier = ">=2.5.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pysource-codegen"
|
||||
version = "0.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/5d/1ab81f5f4eb3fb9203a91ea3cdb7da7b19b38993d669e39000a6454128e1/pysource_codegen-0.6.0.tar.gz", hash = "sha256:0337e3cf3639f017567ab298684c78eb15c877b093965259c725a13a4917be4e", size = 157342 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/e4/c3a105d7d43bf237b18169eb1a06e1b93dc0daa96e74efa14c6d908979b4/pysource_codegen-0.6.0-py3-none-any.whl", hash = "sha256:858f2bbed6de7a7b7e9bbea5cb8212ec7e66fa7f2a7ba433fe05f72438017b30", size = 19489 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pysource-minimize"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "astunparse" },
|
||||
{ name = "click" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/74/a095ade9a0d8b33b3eec3d84510dc2ff4327cd9fcd8340e107aaad8721fd/pysource_minimize-0.7.0.tar.gz", hash = "sha256:5fd477cba4d6251912d4e6cd21a19e6de03a72d1da1ec3bb40aa82ce468ddbe1", size = 792926 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/0f/68b7bef766029025412b919bb25694779666dfc09fe6d8604f0034778b3d/pysource_minimize-0.7.0-py3-none-any.whl", hash = "sha256:3d1f3e53ee51a697a1ed9416e243aae5d7bf5e8d21af534108901016bf50d535", size = 13525 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.9.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich-argparse"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/ee/c410251ff6123d4417f2fe8e72c8628f187682b70ce34134a2a3e307a2d5/rich_argparse-1.6.0.tar.gz", hash = "sha256:092083c30da186f25bcdff8b1d47fdfb571288510fb051e0488a72cc3128de13", size = 17499 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/45/54b95bb72bb17c27a7252bee5034955020b5869a33918b660ffc29cbf608/rich_argparse-1.6.0-py3-none-any.whl", hash = "sha256:fbe70a1d821b3f2fa8958cddf0cae131870a6e9faa04ab52b409cb1eda809bd7", size = 20072 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "2.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wheel"
|
||||
version = "0.45.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494 },
|
||||
]
|
Loading…
Add table
Add a link
Reference in a new issue