feat/python-checks (#5651)
All checks were successful
continuous-integration/drone/push Build is passing

port the bash precommit hooks to python in a nice extensible way.

It should have feature parity and be much easier to read and maintain. It also allows for reducing the number of dependencies in the pre commit docker image.

I have tested that the checks can both pass and fail correctly, but it is somewhat possible there might be some edge cases and bugs I have missed. So for a short while after merging if failing ci look at it a little more carefully, in case the checks need some tuning.

Co-authored-by: Darragh Elliott <me@delliott.net>
Reviewed-on: #5651
Reviewed-by: tobiasd <tobiasd@fsfe.org>
Co-authored-by: delliott <delliott@fsfe.org>
Co-committed-by: delliott <delliott@fsfe.org>
This commit was merged in pull request #5651.
This commit is contained in:
2026-02-26 12:40:54 +00:00
committed by tobiasd
parent 5478c2b83e
commit baf96e4d11
23 changed files with 1090 additions and 1086 deletions

View File

@@ -63,22 +63,8 @@ pre-commit:
ty:
glob: "*.py"
run: ty check {staged_files}
structure:
glob:
- "*.xml"
- "*.xhtml"
run: tools/ci-checks/compare-xml-structure.py {staged_files}
general:
glob:
- "*.html"
- "*.xml"
- "*.xhtml"
run: tools/ci-checks/general.sh {staged_files}
non-en-frontpage-news:
glob:
- "*.xml"
- "*.xhtml"
run: tools/ci-checks/check-non-en-frontpage.sh {staged_files}
general-checks:
run: tools/ci-checks/run_checks.py {staged_files}
simple-syntax:
glob:
- "*.xml"

View File

@@ -5,16 +5,12 @@ COPY --from=ghcr.io/astral-sh/uv /uv /uvx /bin/
# Install deps
RUN apt-get update && apt-get install --yes --no-install-recommends \
composer \
curl \
file \
git \
libxml2 \
libxml2-utils \
libxslt1.1 \
mediainfo \
node-less \
npm \
perl-base \
php-zip \
rsync \
shfmt \

View File

@@ -20,6 +20,8 @@ build = "fsfe_website_build:build"
[dependency-groups]
dev = [
"lefthook", # pre-commit hook
"pillow", # image processing
"pyright", # python typechecker
"pytest", # python test runner
"pytest-mock", # helper for mocking in pytest
"reuse", # for enforcing licensing
@@ -33,9 +35,11 @@ dev = [
requires = ["uv_build"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-name = "fsfe_website_build"
module-root = "build"
[tool.pytest]
addopts = ["--import-mode=importlib"]
python_files = ["*_test.py"]
python_functions = ["*_test"]
testpaths = ["build/fsfe_website_build_tests*"]
[tool.ruff.lint]
# See https://docs.astral.sh/ruff/rules/ for information
@@ -84,9 +88,10 @@ ignore = [
"TRY003", # Dont worry about big exceptions in tests
"S101", # Allow asserts in tests
]
"tools/**" = [
"TRY003", # Allow large exception messages in code cli scripting things
]
[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
python_files = ["*_test.py"]
python_functions = ["*_test"]
testpaths = ["build/fsfe_website_build_tests*"]
[tool.uv.build-backend]
module-name = "fsfe_website_build"
module-root = "build"

View File

@@ -30,9 +30,6 @@ in
libffi
go
# Packages for git hooks
mediainfo
perl
file
shfmt
prettier
php84Packages.php-cs-fixer

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Check is there are any frontpage news items that do not have an english translation
#
# select all items which have the front-page tag
exit=0
matched_files="$(grep "<tag.*front-page" --files-with-matches "$@" || true)"
for file in $matched_files; do
base="${file%.*}" # file.xx
base="${base%.*}" # file
sfx=${file##*.} # xhtml
en="$base.en.$sfx" # file.en.xhtml
if [[ ! -f "$en" ]]; then
echo "$file has no english version"
exit=1
fi
done
exit $exit

View File

@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""Module folder of checks."""

View File

@@ -0,0 +1,48 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files do not use absolute links to fsfe.org."""
import logging
import re
import textwrap
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for absolute links."""
absolute_regex = re.compile("https?://fsfe(urope)?.org")
failed_files_links: list[str] = []
for file in files:
failed_files_links.extend(
f"{file} has absolute link {href}"
for href in etree.parse(file).xpath("//*/@href")
if absolute_regex.match(href)
)
return len(failed_files_links) == 0, (
"\n".join(failed_files_links)
+ textwrap.dedent("""
Please do not use links containing "https://fsfe.org". So instead of
<a href="https://fsfe.org/freesoftware/">link</a>
you should use:
<a href="/freesoftware">link</a>
More information about the why and how:
https://docs.fsfe.org/en/techdocs/mainpage/editing/bestpractices#no-absolute-links-to-fsfeorg""")
)

View File

@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files have the proper encoding."""
import logging
import textwrap
from typing import TYPE_CHECKING
from fsfe_website_build.lib.misc import run_command
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml", "xsl"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check correct encoding."""
failed_files: list[str] = []
for file in files:
encoding_result = run_command(["file", "-b", "--mime-encoding", str(file)])
if encoding_result not in ["utf-8", "ascii"]:
failed_files.append(f"{file} has invalid encoding {encoding_result}")
return len(failed_files) == 0, (
"\n".join(failed_files)
+ textwrap.dedent("""
For the FSFE website, we strongly prefer UTF-8 encoded files.
Everything else creates problems. Please change the file encoding in
your text editor or with a special tool.""")
)

View File

@@ -0,0 +1,51 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files do not use fixed language links."""
import logging
import re
import textwrap
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for fixed language links."""
absolute_regex = re.compile(
r"(https?://fsfe(urope)?.org)?/.*\.[a-z]{2}(\.html)?(#.*)?$"
)
failed_files_links: list[str] = []
for file in files:
failed_files_links.extend(
f"{file} has absolute language link {href}"
for href in etree.parse(file).xpath("//*/@href")
if absolute_regex.match(href)
)
return len(failed_files_links) == 0, (
"\n".join(failed_files_links)
+ textwrap.dedent("""
Please do not preset a language in your links to resources on fsfe.org.
So instead of:
<a href="/contribute/web.en.html">link</a>
you should use:
<a href="/contribute/web.html">link</a>
More information about the why and how:
https://docs.fsfe.org/en/techdocs/mainpage/editing/bestpractices#no-fixed-language-in-internal-links""")
)

View File

@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files have alt text for images."""
import logging
import textwrap
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "informational"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for images having alt text."""
failed_files: list[str] = [
f"{file} has an image without alt text"
for file in files
if (parsedFile := etree.parse(file)).xpath(
"//img[not(@alt) or string-length(normalize-space(@alt))=0]"
)
or (
parsedFile.xpath(
"//image[not(@alt) or string-length(normalize-space(@alt))=0]"
)
)
]
return len(failed_files) == 0, (
"\n".join(failed_files)
+ textwrap.dedent("""
This attribute is important if the image cannot be displayed, and for visually
impaired people. You should describe the image so that it makes sense even if
you cannot see it.
More information on alternative text for images:
https://docs.fsfe.org/en/techdocs/mainpage/editing/bestpractices#alternative-text-for-images""")
)

View File

@@ -0,0 +1,73 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files have the correct ratio for images."""
import io
import logging
import re
import textwrap
from typing import TYPE_CHECKING
import requests
from lxml import etree
from PIL import Image
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def _check_image_ratio(image_bytes: bytes) -> bool:
with Image.open(io.BytesIO(image_bytes)) as img:
width, height = img.size
ideal_ratio = 16 / 9
actual_ratio = width / height
tolerance = 0.005 * ideal_ratio
return (ideal_ratio - tolerance) <= actual_ratio <= (ideal_ratio + tolerance)
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for if images have the right ratio."""
fileregex = re.compile(r"^fsfe.org/(news/|events/).*")
failed_files: list[str] = []
for file in [file for file in files if fileregex.match(str(file))]:
for imageurl in etree.parse(file).xpath("//image/@url"):
# the url may be to a remote or local image
is_remote = imageurl.startswith(("http://", "https://"))
image = b""
if is_remote:
response = requests.get(imageurl, timeout=10)
if not response.ok:
message = "Failed to fetch image"
logger.error(message)
raise RuntimeError(message)
image: bytes = response.content
else:
# we should not be committing news or events files
# with images in repo anymore
# and figuring out how to open paths correctly is
# actually a little tricky
# so not implementing this for now, and can be done later if needed
message = "Image ratio check requires remote images"
logger.error(message)
raise RuntimeError(message)
if not _check_image_ratio(image):
failed_files.append(f"{file} has {imageurl=} with invalid ratio")
return len(failed_files) == 0, (
"\n".join(failed_files)
+ textwrap.dedent("""
16:9 ratio for preview is mandatory for news items.
More information on preview images:
https://docs.fsfe.org/en/techdocs/mainpage/editing/bestpractices#preview-image""")
)

View File

@@ -0,0 +1,74 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files have matching filenames and newsdates."""
import logging
import re
import textwrap
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for mismatched file names and newsdates."""
fileregex = re.compile(
r"^fsfe.org/(news/20[0-9]{2}/|news/nl/|news/podcast/20[0-9]{2}/|events/20[0-9]{2}/).*"
)
naming_regex = re.compile(
""
r"^((nl-20[0-9]{4})|episode-(special-)?[0-9]{1,3}"
r"|(news|event)-20[0-9]{6}-[0-9]{2})\.[a-z]{2}\.(xml|xhtml)$"
)
newsdate_regex = re.compile(r"^20[0-9]{2}-[0-9]{2}-[0-9]{2}$")
failed_file_names: list[str] = []
failed_file_newsdate: list[str] = []
for file in [file for file in files if fileregex.match(str(file))]:
# check filename
if not naming_regex.match(file.name):
failed_file_names.append(f"{file} is named incorrectly")
# check file newsdate
failed_file_newsdate.extend(
f"{file} has invalid newsdate"
for file in files
if any(
not newsdate_regex.match(newsdate)
for newsdate in etree.parse(file).xpath("//html/@newsdate")
)
)
# TODO: implement a way to compare date from name to date in newsdate
# and ensure they are the same
# Maybe use a date parser on different substrings in the name based on source?
return (
len(failed_file_names) == 0 and len(failed_file_newsdate) == 0,
"\n".join(failed_file_names)
+ textwrap.dedent("""
The scheme is:
* "news-20YYMMDD-01.en.xhtml" for news
* "nl-20YYMM.en.xhtml" for newsletters
* "episode-N.en.xhtml" for podcast episodes
* "event-20YYMMDD-01.en.xml" for events
If there is more than one news item per date, count the "-01"
onwards. Of course, the ".en" can also be the code for another
language we support.
""")
+ "\n".join(failed_file_newsdate)
+ textwrap.dedent("""
The scheme is \"20YY-MM-DD\", so the respective line should look
something like this: <html newsdate=\"2020-01-01\">"""),
)

View File

@@ -0,0 +1,83 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files do not add new tags."""
import logging
import re
import textwrap
from pathlib import Path
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
logger = logging.getLogger(__name__)
CHECK_TYPE = "informational"
ALLOWED_EXTENSIONS = {".xhtml", ".xml", "xsl"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for newly added tags."""
fileregex = re.compile(r"^fsfe.org/(news/|events/).*")
new_tags: list[str] = [] # List of new tag messages
# Get all relevant files in the news and events directories
# For seeing if tags already exist
base_dir = Path()
relevant_files: list[Path] = []
for pattern in [
"fsfe.org/news/**/*.xhtml",
"fsfe.org/news/**/*.xml",
"fsfe.org/news/**/*.xsl",
"fsfe.org/events/**/*.xhtml",
"fsfe.org/events/**/*.xml",
"fsfe.org/events/**/*.xsl",
]:
relevant_files.extend(base_dir.glob(pattern))
for file in files:
if not fileregex.match(str(file)):
continue
# parse the current file
tree = etree.parse(file)
# iterate over tag keys
for tag_key in tree.xpath("//tag/@key"):
# Check if this tag exists in any other file
found_in_other = False
for other_file in relevant_files:
# Ignore the file itself
if other_file == file.relative_to(base_dir):
continue
other_tree = etree.parse(
other_file,
)
# check if there are kays with the key in the other file
other_tags = other_tree.xpath(f'//tag[@key="{tag_key}"]')
if other_tags:
found_in_other = True
if not found_in_other:
new_tags.append(f"{file} adds new tag: {tag_key}")
return len(new_tags) == 0, (
"\n".join(new_tags)
+ textwrap.dedent("""
Please make sure that you use already used tags, and only introduce a
new tag e.g. if it's about a new campaign that will be more often
mentioned in news or events. If you feel unsure, please ask
<web@lists.fsfe.org>.
Here you will find the currently used tags:
https://fsfe.org/tags/tags.html
Please make another commit to replace a new tag with an already
existing one unless you are really sure. Thank you.""")
)

View File

@@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files do not have frontpage news in non english."""
import logging
import textwrap
from pathlib import Path
from typing import TYPE_CHECKING
from fsfe_website_build.lib.misc import (
get_basepath,
)
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for frontpage news."""
failed_files: list[str] = [
str(file)
for file in files
if etree.parse(file).xpath("//tag[@key='front-page']")
and not (
(file_path := Path(file)).parent
/ f"{get_basepath(file_path).name}.en{file_path.suffix}"
).exists()
]
return len(failed_files) == 0, (
"\n".join(failed_files)
+ textwrap.dedent("""
The above files have no english version
and are marked for the front page:""")
)

View File

@@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files do not have unobfustucated emails."""
import logging
import re
import textwrap
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "informational"
ALLOWED_EXTENSIONS = {".xhtml", ".xml", "xsl"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for plaintext emails."""
email_regex = re.compile(
r"[A-Za-z-+]*@fsfe.org(?!<\/email)", re.IGNORECASE | re.MULTILINE
)
failed_files: list[str] = [
f"{file} has plaintext email"
for file in files
if email_regex.search(file.read_text())
]
return len(failed_files) == 0, (
"\n".join(failed_files)
+ textwrap.dedent("""
Plaintext email addresses are trivial to crawl for by bots:
There is a simple solution: wrap the email address(es) in <email>...</email>.
More information on obfuscated email addresses:
https://fsfe.org/contribute/web/features.html#emails""")
)

View File

@@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files do not use style elements."""
import logging
import textwrap
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "informational"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for style elements."""
failed_files: list[str] = [
f"{file} contains a style element"
for file in files
if etree.parse(file).xpath("//*[@style]")
]
return len(failed_files) == 0, (
"\n".join(failed_files)
+ textwrap.dedent("""
Please do not use style attributes to design an element. So instead of:
<p style="color: red;">text</p>
use CSS classes instead, or create them if necessary.
More information why this is bad style, and what to do instead:
https://docs.fsfe.org/en/techdocs/mainpage/editing/bestpractices#no-in-line-css""")
)

View File

@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files do not use style elements."""
import logging
import textwrap
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for style elements."""
failed_files: list[str] = [
f"{file} contains a style element"
for file in files
if etree.parse(file).xpath("//style")
]
return len(failed_files) == 0, (
"\n".join(failed_files)
+ textwrap.dedent("""
Please do not use <style> elements to define CSS rules for a file.
More information why this is bad style, and what to do instead:
https://docs.fsfe.org/en/techdocs/mainpage/editing/bestpractices#no-in-line-css""")
)

View File

@@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files have one version and that it is an integer."""
import logging
from typing import TYPE_CHECKING
from lxml import etree
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]: # noqa: ARG001
"""Check for missing/too many/non integer versions."""
invalid_files: list[str] = [
f"{file} has missing/too many/non integer version attribute"
for file in files
if (versions := etree.parse(file).xpath("/*/version")) is None
or len(versions) != 1
or not versions[0].text.isdigit()
]
return len(invalid_files) == 0, "\n".join(invalid_files)

View File

@@ -0,0 +1,81 @@
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files match xml structure across languages."""
import logging
from collections import defaultdict
from typing import TYPE_CHECKING
from fsfe_website_build.lib.checks import (
compare_files,
)
from fsfe_website_build.lib.misc import (
get_basepath,
get_version,
)
if TYPE_CHECKING:
import multiprocessing.pool
from pathlib import Path
logger = logging.getLogger(__name__)
CHECK_TYPE = "critical"
ALLOWED_EXTENSIONS = {".xhtml", ".xml"}
def _job(master: Path, other: Path, whitelist: set[str]) -> str | None:
"""Return a result string for starmap."""
try:
if get_version(master) != get_version(other):
return None
errs = compare_files(master, other, whitelist)
return (
f"There were differences between {master} and {other}:\n{'\n'.join(errs)}"
if errs
else None
)
except Exception as e:
return f"Exception occurred comparing {master} and {other}: ERROR {e}"
def check(files: list[Path], pool: multiprocessing.pool.Pool) -> tuple[bool, str]:
"""Check for xml structure differences."""
whitelist = {
"//discussion/@href", # Mastodon links can be in different langs
"/html/translator", # the translator
"//image/@alt", # Image alt text for title image
"//img/@alt", # Image alt text
"//input[@name='language']", # Input language types
"//label[@for='address']",
"//label[@for='email']",
"//label[@for='name']", # city and many others
"//label[@for='phone']",
"//label[@for='zip']", # zip code localisation
"//profileimage/@alt", # Profilemage alt text for about/people images
"//track/@label", # Language label, used in some track elements
"//track/@srclang", # Languages, used in some track elements
}
groups: defaultdict[tuple[Path, str], list[Path]] = defaultdict(list)
for file in files:
path = file.resolve()
groups[(get_basepath(path), path.suffix)].append(path)
tasks: list[tuple[Path, Path, set[str]]] = []
for path_data, paths in groups.items():
basepath, suffix = path_data
if (master := basepath.parent / f"{basepath.name}.en{suffix}").exists():
tasks.extend((master, path, whitelist) for path in paths if path != master)
else:
logger.warning(
"No english translation of %s with suffix %s exists - skipping",
basepath,
suffix,
)
filtered_results = [
result for result in pool.starmap(_job, tasks) if result is not None
]
return len(filtered_results) == 0, "\n".join(filtered_results)

View File

@@ -1,127 +0,0 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Check that the passed files match xml structure across languages."""
import argparse
import logging
import multiprocessing
import sys
from collections import defaultdict
from pathlib import Path
from fsfe_website_build.lib.checks import (
compare_files,
)
from fsfe_website_build.lib.misc import (
get_basepath,
get_version,
)
logger = logging.getLogger(__name__)
def _job(master: Path, other: Path, whitelist: set[str]) -> str | None:
"""Return a result string for starmap."""
try:
if get_version(master) != get_version(other):
return None
errs = compare_files(master, other, whitelist)
return (
f"There were differences between {master} and {other}:\n{'\n'.join(errs)}"
if errs
else None
)
except Exception as e:
return f"Exception occurred comparing {master} and {other}: ERROR {e}"
def main() -> None:
"""Check that the passed files match xml structure across languages."""
parser = argparse.ArgumentParser(
description="Compare XML structure and attributes. "
"Use --multi for space-separated list + parallel compares.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("files", nargs="*", help="XML file(s)")
parser.add_argument(
"-w",
"--whitelist",
nargs="+",
type=str,
default=[
"//body/@class", # Top level body classes
"//discussion/@href", # Mastodon links can be in different langs
"//image/@alt", # Image alt text for title image
"//img/@alt", # Image alt text
"//input[@name='language']", # Input language types
"//label[@for='address']",
"//label[@for='email']",
"//label[@for='name']", # city and many others
"//label[@for='phone']",
"//label[@for='zip']", # zip code localisation
"//profileimage/@alt", # Profilemage alt text for about/people images
"//track/@label", # Language label, used in some track elements
"//track/@srclang", # Languages, used in some track elements
"/html/translator", # the translator
],
help="XPATHS that we then ignore.",
)
parser.add_argument(
"--log-level",
type=str,
default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
help="Set the logging level",
)
parser.add_argument(
"-j",
"--jobs",
type=int,
default=multiprocessing.cpu_count(),
help="Parallel workers",
)
args = parser.parse_args()
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
level=args.log_level,
)
groups: defaultdict[tuple[Path, str], list[Path]] = defaultdict(list)
for file in args.files:
path = Path(file).resolve()
groups[(get_basepath(path), path.suffix)].append(path)
tasks: list[tuple[Path, Path, set[str]]] = []
for path_data, paths in groups.items():
basepath, suffix = path_data
if (master := basepath.parent / f"{basepath.name}.en{suffix}").exists():
tasks.extend(
(master, path, args.whitelist) for path in paths if path != master
)
else:
logger.warning(
"No english translation of %s with suffix %s exists - skipping",
basepath,
suffix,
)
with multiprocessing.Pool(processes=args.jobs) as pool:
filtered_results = [
result for result in pool.starmap(_job, tasks) if result is not None
]
if filtered_results:
for result in filtered_results:
logger.info(result)
logger.error("Some comparisons failed, exiting")
sys.exit(1)
logger.info("All comparisons succeeded, success")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

167
tools/ci-checks/run_checks.py Executable file
View File

@@ -0,0 +1,167 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""Runs a variety of checks."""
import argparse
import importlib.util
import logging
import multiprocessing
import multiprocessing.pool
import sys
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from types import ModuleType
logger = logging.getLogger(__name__)
def load_check_modules(check_dir: Path) -> tuple[list[ModuleType], list[ModuleType]]:
"""Dynamically load all check modules from a directory."""
# Ensure the check_dir is in sys.path so it's importable
if str(check_dir) not in sys.path:
sys.path.insert(0, str(check_dir))
critical_modules: list[ModuleType] = []
informational_modules: list[ModuleType] = []
if not check_dir.exists() or not check_dir.is_dir():
raise FileNotFoundError(f"Check directory not found: {check_dir}")
for file in sorted(check_dir.iterdir()):
if file.name.startswith("check_") and file.suffix == ".py":
module_name = f"checks.{file.stem}"
# Load module from file
spec = importlib.util.spec_from_file_location(module_name, file)
if spec is None or spec.loader is None:
raise RuntimeError("Failed to load %s.", file)
module = importlib.util.module_from_spec(spec)
# Register it to prevent pickle issues
sys.modules[module_name] = module
spec.loader.exec_module(module)
# Ensure it has the required function
if (
not hasattr(module, "check")
or not hasattr(module, "ALLOWED_EXTENSIONS")
or not hasattr(module, "CHECK_TYPE")
or module.CHECK_TYPE not in ["critical", "informational"]
or not callable(module.check)
):
raise RuntimeError("%s. missing some required attr", file)
if module.CHECK_TYPE == "critical":
critical_modules.append(module)
elif module.CHECK_TYPE == "informational":
informational_modules.append(module)
else:
raise RuntimeError("%s CHECK_TYPE invalid", file)
return critical_modules, informational_modules
def run_module_list(
files: list[Path], modules: list[ModuleType], pool: multiprocessing.pool.Pool
) -> bool:
"""Run a list of modules on passed files, using passed pool."""
all_passed = True
for module in modules:
# Filter files by extension based on ALLOWED_EXTENSIONS
filtered_files: list[Path] = [
file for file in files if file.suffix in module.ALLOWED_EXTENSIONS
]
# Skip check if no relevant files
if not filtered_files:
logger.debug("%s: no relevant files, skipping", module)
continue
logger.debug("%s: Running", module)
success, message = module.check(filtered_files, pool)
if not success:
logger.error(message)
all_passed = False
else:
logger.debug("Check Passed!")
return all_passed
def main() -> None:
"""Check the passed files."""
parser = argparse.ArgumentParser(
description="Check passed files.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("files", nargs="*", type=Path, help="Files to be checked")
parser.add_argument(
"--log-level",
type=str,
default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
help="Set the logging level",
)
parser.add_argument(
"-j",
"--jobs",
type=int,
default=multiprocessing.cpu_count(),
help="Parallel workers",
)
args = parser.parse_args()
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
level=args.log_level,
)
try:
critical_modules, informational_modules = load_check_modules(
Path("./tools/ci-checks/checks")
)
except Exception:
logger.exception("Error loading check modules: %s")
sys.exit(1)
with multiprocessing.Pool(processes=args.jobs) as pool:
# Run each check
try:
logger.info("Beginning Informational checks!")
logger.info(
"One may commit if these fail,"
" but should consider fixing the issues raised!\n\n"
)
informational_checks_successful = run_module_list(
args.files, informational_modules, pool
)
logger.info("Informational Checks finished!\n\n")
logger.info("Beginning Critical checks!")
logger.info("One must fix raised issues before being able to commit!\n\n")
critical_checks_successful = run_module_list(
args.files, critical_modules, pool
)
logger.info("Critical Checks finished!\n\n")
if not informational_checks_successful:
logger.warning(
"Some informational checks failed,"
" check messages and consider errors!"
)
else:
logger.info("All informational checks passed.")
if not critical_checks_successful:
logger.error(
"Some critical checks failed, check messages and fix errors!"
)
sys.exit(1)
else:
logger.info("All critical checks passed.")
sys.exit(0)
except Exception:
logger.exception("Check failed to run correctly: %s")
sys.exit(1)
if __name__ == "__main__":
main()

369
uv.lock generated
View File

@@ -13,15 +13,15 @@ wheels = [
[[package]]
name = "beautifulsoup4"
version = "4.14.2"
version = "4.14.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "soupsieve" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" },
{ url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
]
[[package]]
@@ -35,11 +35,11 @@ wheels = [
[[package]]
name = "certifi"
version = "2025.11.12"
version = "2026.1.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
]
[[package]]
@@ -102,14 +102,14 @@ wheels = [
[[package]]
name = "click"
version = "8.3.0"
version = "8.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
@@ -123,11 +123,11 @@ wheels = [
[[package]]
name = "cssselect"
version = "1.3.0"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/0a/c3ea9573b1dc2e151abfe88c7fe0c26d1892fe6ed02d0cdb30f0d57029d5/cssselect-1.3.0.tar.gz", hash = "sha256:57f8a99424cfab289a1b6a816a43075a4b00948c86b4dcf3ef4ee7e15f7ab0c7", size = 42870, upload-time = "2025-03-10T09:30:29.638Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ec/2e/cdfd8b01c37cbf4f9482eefd455853a3cf9c995029a46acd31dfaa9c1dd6/cssselect-1.4.0.tar.gz", hash = "sha256:fdaf0a1425e17dfe8c5cf66191d211b357cf7872ae8afc4c6762ddd8ac47fc92", size = 40589, upload-time = "2026-01-29T07:00:26.701Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ee/58/257350f7db99b4ae12b614a36256d9cc870d71d9e451e79c2dc3b23d7c3c/cssselect-1.3.0-py3-none-any.whl", hash = "sha256:56d1bf3e198080cc1667e137bc51de9cadfca259f03c2d4e09037b3e01e30f0d", size = 18786, upload-time = "2025-03-10T09:30:28.048Z" },
{ url = "https://files.pythonhosted.org/packages/20/0c/7bb51e3acfafd16c48875bf3db03607674df16f5b6ef8d056586af7e2b8b/cssselect-1.4.0-py3-none-any.whl", hash = "sha256:c0ec5c0191c8ee39fcc8afc1540331d8b55b0183478c50e9c8a79d44dbceb1d8", size = 18540, upload-time = "2026-01-29T07:00:24.994Z" },
]
[[package]]
@@ -156,6 +156,8 @@ dependencies = [
[package.dev-dependencies]
dev = [
{ name = "lefthook" },
{ name = "pillow" },
{ name = "pyright" },
{ name = "pytest" },
{ name = "pytest-mock" },
{ name = "reuse" },
@@ -179,6 +181,8 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
{ name = "lefthook" },
{ name = "pillow" },
{ name = "pyright" },
{ name = "pytest" },
{ name = "pytest-mock" },
{ name = "reuse" },
@@ -220,20 +224,20 @@ wheels = [
[[package]]
name = "joblib"
version = "1.5.2"
version = "1.5.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" }
sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" },
{ url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
]
[[package]]
name = "lefthook"
version = "2.0.4"
version = "2.0.15"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fa/9b/51e950ae394e77b606cd220a2cfbe5216b9df52abe4ac3491408c6665b8d/lefthook-2.0.4.tar.gz", hash = "sha256:4962717a4db4474929ca269e8f4766ca99a6cf88db399f98ffc36d49939f3359", size = 54466984, upload-time = "2025-11-13T09:08:23.148Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/8f/e90724128f481c637b7e9343b6535622cfa136541959a49f8a3de0d0e7a5/lefthook-2.0.15.tar.gz", hash = "sha256:8a32c9f2d44f0ff0f3e3ab48f9802ca10f1222b491f3f14e03f577ec175b6649", size = 50175274, upload-time = "2026-01-13T10:02:58.76Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/7f/dc7c506d1df93affb720910c7ca57a45064a997ea551966734063f8c7512/lefthook-2.0.4-py3-none-any.whl", hash = "sha256:2aa8c4d3ccd3b9d12d31967d58817c54390bc175034e699143bc29d81add57eb", size = 54721020, upload-time = "2025-11-13T09:08:18.644Z" },
{ url = "https://files.pythonhosted.org/packages/7c/d5/243d967f541d422a7252f92dbcc201cfd588bd404a66935baacc83be49c5/lefthook-2.0.15-py3-none-any.whl", hash = "sha256:54b174520f18a4fa2545ff1e5eae4c4d2515539fb25b8c4fe04b9224a2ff07ee", size = 50416709, upload-time = "2026-01-13T10:02:55.761Z" },
]
[[package]]
@@ -338,21 +342,63 @@ wheels = [
]
[[package]]
name = "packaging"
version = "25.0"
name = "nodeenv"
version = "1.10.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
{ 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 = "packaging"
version = "26.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
]
[[package]]
name = "pillow"
version = "12.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
{ url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
{ url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
{ url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
{ url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
{ url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
{ url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
{ url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
{ url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
{ url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
{ url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
{ url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
{ url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
{ url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
{ url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
{ url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
{ url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
{ url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
{ url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
{ url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
{ url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
{ url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
{ url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
]
[[package]]
name = "platformdirs"
version = "4.5.0"
version = "4.9.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
{ url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" },
]
[[package]]
@@ -366,11 +412,11 @@ wheels = [
[[package]]
name = "pycparser"
version = "2.23"
version = "3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]
[[package]]
@@ -382,9 +428,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pyright"
version = "1.1.408"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodeenv" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" },
]
[[package]]
name = "pytest"
version = "9.0.1"
version = "9.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -393,9 +452,9 @@ dependencies = [
{ name = "pluggy" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" },
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
]
[[package]]
@@ -412,23 +471,23 @@ wheels = [
[[package]]
name = "python-debian"
version = "1.0.1"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "charset-normalizer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bf/4b/3c4cf635311b6203f17c2d693dc15e898969983dd3f729bee3c428aa60d4/python-debian-1.0.1.tar.gz", hash = "sha256:3ada9b83a3d671b58081782c0969cffa0102f6ce433fbbc7cf21275b8b5cc771", size = 127249, upload-time = "2025-03-11T12:27:27.245Z" }
sdist = { url = "https://files.pythonhosted.org/packages/eb/f4/ec7ba072029399a89a2670bcef4df79c52f2efaa672f86e0a86252313333/python_debian-1.1.0.tar.gz", hash = "sha256:afe3c7267d81c29c79ab44d803a0756d0796b0e41bb0a05c5cafcdd2b980d4e6", size = 127848, upload-time = "2026-02-09T02:13:24.491Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/15/e8096189b18dda72e4923622abc10b021ecff723b397e22eff29fb86637b/python_debian-1.0.1-py3-none-any.whl", hash = "sha256:8f137c230c1d9279c2ac892b35915068b2aca090c9fd3da5671ff87af32af12c", size = 137453, upload-time = "2025-03-11T12:27:25.014Z" },
{ url = "https://files.pythonhosted.org/packages/0a/66/af49103279c07900b89b7ebac53111910aa8eb6d898be0b47220036b0b03/python_debian-1.1.0-py3-none-any.whl", hash = "sha256:3e553b6d0b886272a26649e13106e33c382bcd21c80b09f5c0c81fc7c8c8c743", size = 137984, upload-time = "2026-02-09T02:13:22.54Z" },
]
[[package]]
name = "python-iso639"
version = "2025.11.11"
version = "2026.1.31"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/89/6f/45bc5ae1c132ab7852a8642d66d25ffff6e4b398195127ac66158d3b5f4d/python_iso639-2025.11.11.tar.gz", hash = "sha256:75fab30f1a0f46b4e8161eafb84afe4ecd07eaada05e2c5364f14b0f9c864477", size = 173897, upload-time = "2025-11-11T15:23:00.893Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/da/701fc47ea3b0579a8ae489d50d5b54f2ef3aeb7768afd31db1d1cfe9f24e/python_iso639-2026.1.31.tar.gz", hash = "sha256:55a1612c15e5fbd3a1fa269a309cbf1e7c13019356e3d6f75bb435ed44c45ddb", size = 174144, upload-time = "2026-01-31T15:04:48.105Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/69/081960288e4cd541cbdb90e1768373e1198b040bf2ae40cd25b9c9799205/python_iso639-2025.11.11-py3-none-any.whl", hash = "sha256:02ea4cfca2c189b5665e4e8adc8c17c62ab6e4910932541a23baddea33207ea2", size = 167723, upload-time = "2025-11-11T15:22:59.819Z" },
{ url = "https://files.pythonhosted.org/packages/5b/3a/03ee682b04099e6b02b591955851b0347deb2e3691ae850112000c54ba12/python_iso639-2026.1.31-py3-none-any.whl", hash = "sha256:b2c48fa1300af1299dff4f1e1995ad1059996ed9f22270ea2d6d6bdc5fb03d4c", size = 167757, upload-time = "2026-01-31T15:04:46.458Z" },
]
[[package]]
@@ -442,38 +501,42 @@ wheels = [
[[package]]
name = "regex"
version = "2025.11.3"
version = "2026.1.15"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669, upload-time = "2025-11-03T21:34:22.089Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/31/e9/f6e13de7e0983837f7b6d238ad9458800a874bf37c264f7923e63409944c/regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6", size = 489089, upload-time = "2025-11-03T21:32:50.027Z" },
{ url = "https://files.pythonhosted.org/packages/a3/5c/261f4a262f1fa65141c1b74b255988bd2fa020cc599e53b080667d591cfc/regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4", size = 291059, upload-time = "2025-11-03T21:32:51.682Z" },
{ url = "https://files.pythonhosted.org/packages/8e/57/f14eeb7f072b0e9a5a090d1712741fd8f214ec193dba773cf5410108bb7d/regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73", size = 288900, upload-time = "2025-11-03T21:32:53.569Z" },
{ url = "https://files.pythonhosted.org/packages/3c/6b/1d650c45e99a9b327586739d926a1cd4e94666b1bd4af90428b36af66dc7/regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f", size = 799010, upload-time = "2025-11-03T21:32:55.222Z" },
{ url = "https://files.pythonhosted.org/packages/99/ee/d66dcbc6b628ce4e3f7f0cbbb84603aa2fc0ffc878babc857726b8aab2e9/regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d", size = 864893, upload-time = "2025-11-03T21:32:57.239Z" },
{ url = "https://files.pythonhosted.org/packages/bf/2d/f238229f1caba7ac87a6c4153d79947fb0261415827ae0f77c304260c7d3/regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be", size = 911522, upload-time = "2025-11-03T21:32:59.274Z" },
{ url = "https://files.pythonhosted.org/packages/bd/3d/22a4eaba214a917c80e04f6025d26143690f0419511e0116508e24b11c9b/regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db", size = 803272, upload-time = "2025-11-03T21:33:01.393Z" },
{ url = "https://files.pythonhosted.org/packages/84/b1/03188f634a409353a84b5ef49754b97dbcc0c0f6fd6c8ede505a8960a0a4/regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62", size = 787958, upload-time = "2025-11-03T21:33:03.379Z" },
{ url = "https://files.pythonhosted.org/packages/99/6a/27d072f7fbf6fadd59c64d210305e1ff865cc3b78b526fd147db768c553b/regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f", size = 859289, upload-time = "2025-11-03T21:33:05.374Z" },
{ url = "https://files.pythonhosted.org/packages/9a/70/1b3878f648e0b6abe023172dacb02157e685564853cc363d9961bcccde4e/regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02", size = 850026, upload-time = "2025-11-03T21:33:07.131Z" },
{ url = "https://files.pythonhosted.org/packages/dd/d5/68e25559b526b8baab8e66839304ede68ff6727237a47727d240006bd0ff/regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed", size = 789499, upload-time = "2025-11-03T21:33:09.141Z" },
{ url = "https://files.pythonhosted.org/packages/fc/df/43971264857140a350910d4e33df725e8c94dd9dee8d2e4729fa0d63d49e/regex-2025.11.3-cp314-cp314-win32.whl", hash = "sha256:795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4", size = 271604, upload-time = "2025-11-03T21:33:10.9Z" },
{ url = "https://files.pythonhosted.org/packages/01/6f/9711b57dc6894a55faf80a4c1b5aa4f8649805cb9c7aef46f7d27e2b9206/regex-2025.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad", size = 280320, upload-time = "2025-11-03T21:33:12.572Z" },
{ url = "https://files.pythonhosted.org/packages/f1/7e/f6eaa207d4377481f5e1775cdeb5a443b5a59b392d0065f3417d31d80f87/regex-2025.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f", size = 273372, upload-time = "2025-11-03T21:33:14.219Z" },
{ url = "https://files.pythonhosted.org/packages/c3/06/49b198550ee0f5e4184271cee87ba4dfd9692c91ec55289e6282f0f86ccf/regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc", size = 491985, upload-time = "2025-11-03T21:33:16.555Z" },
{ url = "https://files.pythonhosted.org/packages/ce/bf/abdafade008f0b1c9da10d934034cb670432d6cf6cbe38bbb53a1cfd6cf8/regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49", size = 292669, upload-time = "2025-11-03T21:33:18.32Z" },
{ url = "https://files.pythonhosted.org/packages/f9/ef/0c357bb8edbd2ad8e273fcb9e1761bc37b8acbc6e1be050bebd6475f19c1/regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536", size = 291030, upload-time = "2025-11-03T21:33:20.048Z" },
{ url = "https://files.pythonhosted.org/packages/79/06/edbb67257596649b8fb088d6aeacbcb248ac195714b18a65e018bf4c0b50/regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95", size = 807674, upload-time = "2025-11-03T21:33:21.797Z" },
{ url = "https://files.pythonhosted.org/packages/f4/d9/ad4deccfce0ea336296bd087f1a191543bb99ee1c53093dcd4c64d951d00/regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009", size = 873451, upload-time = "2025-11-03T21:33:23.741Z" },
{ url = "https://files.pythonhosted.org/packages/13/75/a55a4724c56ef13e3e04acaab29df26582f6978c000ac9cd6810ad1f341f/regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9", size = 914980, upload-time = "2025-11-03T21:33:25.999Z" },
{ url = "https://files.pythonhosted.org/packages/67/1e/a1657ee15bd9116f70d4a530c736983eed997b361e20ecd8f5ca3759d5c5/regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d", size = 812852, upload-time = "2025-11-03T21:33:27.852Z" },
{ url = "https://files.pythonhosted.org/packages/b8/6f/f7516dde5506a588a561d296b2d0044839de06035bb486b326065b4c101e/regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6", size = 795566, upload-time = "2025-11-03T21:33:32.364Z" },
{ url = "https://files.pythonhosted.org/packages/d9/dd/3d10b9e170cc16fb34cb2cef91513cf3df65f440b3366030631b2984a264/regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154", size = 868463, upload-time = "2025-11-03T21:33:34.459Z" },
{ url = "https://files.pythonhosted.org/packages/f5/8e/935e6beff1695aa9085ff83195daccd72acc82c81793df480f34569330de/regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267", size = 854694, upload-time = "2025-11-03T21:33:36.793Z" },
{ url = "https://files.pythonhosted.org/packages/92/12/10650181a040978b2f5720a6a74d44f841371a3d984c2083fc1752e4acf6/regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379", size = 799691, upload-time = "2025-11-03T21:33:39.079Z" },
{ url = "https://files.pythonhosted.org/packages/67/90/8f37138181c9a7690e7e4cb388debbd389342db3c7381d636d2875940752/regex-2025.11.3-cp314-cp314t-win32.whl", hash = "sha256:4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38", size = 274583, upload-time = "2025-11-03T21:33:41.302Z" },
{ url = "https://files.pythonhosted.org/packages/8f/cd/867f5ec442d56beb56f5f854f40abcfc75e11d10b11fdb1869dd39c63aaf/regex-2025.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de", size = 284286, upload-time = "2025-11-03T21:33:43.324Z" },
{ url = "https://files.pythonhosted.org/packages/20/31/32c0c4610cbc070362bf1d2e4ea86d1ea29014d400a6d6c2486fcfd57766/regex-2025.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801", size = 274741, upload-time = "2025-11-03T21:33:45.557Z" },
{ url = "https://files.pythonhosted.org/packages/52/0a/47fa888ec7cbbc7d62c5f2a6a888878e76169170ead271a35239edd8f0e8/regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac", size = 489170, upload-time = "2026-01-14T23:16:19.835Z" },
{ url = "https://files.pythonhosted.org/packages/ac/c4/d000e9b7296c15737c9301708e9e7fbdea009f8e93541b6b43bdb8219646/regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6", size = 291146, upload-time = "2026-01-14T23:16:21.541Z" },
{ url = "https://files.pythonhosted.org/packages/f9/b6/921cc61982e538682bdf3bdf5b2c6ab6b34368da1f8e98a6c1ddc503c9cf/regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2", size = 288986, upload-time = "2026-01-14T23:16:23.381Z" },
{ url = "https://files.pythonhosted.org/packages/ca/33/eb7383dde0bbc93f4fb9d03453aab97e18ad4024ac7e26cef8d1f0a2cff0/regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846", size = 799098, upload-time = "2026-01-14T23:16:25.088Z" },
{ url = "https://files.pythonhosted.org/packages/27/56/b664dccae898fc8d8b4c23accd853f723bde0f026c747b6f6262b688029c/regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b", size = 864980, upload-time = "2026-01-14T23:16:27.297Z" },
{ url = "https://files.pythonhosted.org/packages/16/40/0999e064a170eddd237bae9ccfcd8f28b3aa98a38bf727a086425542a4fc/regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e", size = 911607, upload-time = "2026-01-14T23:16:29.235Z" },
{ url = "https://files.pythonhosted.org/packages/07/78/c77f644b68ab054e5a674fb4da40ff7bffb2c88df58afa82dbf86573092d/regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde", size = 803358, upload-time = "2026-01-14T23:16:31.369Z" },
{ url = "https://files.pythonhosted.org/packages/27/31/d4292ea8566eaa551fafc07797961c5963cf5235c797cc2ae19b85dfd04d/regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5", size = 775833, upload-time = "2026-01-14T23:16:33.141Z" },
{ url = "https://files.pythonhosted.org/packages/ce/b2/cff3bf2fea4133aa6fb0d1e370b37544d18c8350a2fa118c7e11d1db0e14/regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34", size = 788045, upload-time = "2026-01-14T23:16:35.005Z" },
{ url = "https://files.pythonhosted.org/packages/8d/99/2cb9b69045372ec877b6f5124bda4eb4253bc58b8fe5848c973f752bc52c/regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75", size = 859374, upload-time = "2026-01-14T23:16:36.919Z" },
{ url = "https://files.pythonhosted.org/packages/09/16/710b0a5abe8e077b1729a562d2f297224ad079f3a66dce46844c193416c8/regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e", size = 763940, upload-time = "2026-01-14T23:16:38.685Z" },
{ url = "https://files.pythonhosted.org/packages/dd/d1/7585c8e744e40eb3d32f119191969b91de04c073fca98ec14299041f6e7e/regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160", size = 850112, upload-time = "2026-01-14T23:16:40.646Z" },
{ url = "https://files.pythonhosted.org/packages/af/d6/43e1dd85df86c49a347aa57c1f69d12c652c7b60e37ec162e3096194a278/regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1", size = 789586, upload-time = "2026-01-14T23:16:42.799Z" },
{ url = "https://files.pythonhosted.org/packages/93/38/77142422f631e013f316aaae83234c629555729a9fbc952b8a63ac91462a/regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1", size = 271691, upload-time = "2026-01-14T23:16:44.671Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a9/ab16b4649524ca9e05213c1cdbb7faa85cc2aa90a0230d2f796cbaf22736/regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903", size = 280422, upload-time = "2026-01-14T23:16:46.607Z" },
{ url = "https://files.pythonhosted.org/packages/be/2a/20fd057bf3521cb4791f69f869635f73e0aaf2b9ad2d260f728144f9047c/regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705", size = 273467, upload-time = "2026-01-14T23:16:48.967Z" },
{ url = "https://files.pythonhosted.org/packages/ad/77/0b1e81857060b92b9cad239104c46507dd481b3ff1fa79f8e7f865aae38a/regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8", size = 492073, upload-time = "2026-01-14T23:16:51.154Z" },
{ url = "https://files.pythonhosted.org/packages/70/f3/f8302b0c208b22c1e4f423147e1913fd475ddd6230565b299925353de644/regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf", size = 292757, upload-time = "2026-01-14T23:16:53.08Z" },
{ url = "https://files.pythonhosted.org/packages/bf/f0/ef55de2460f3b4a6da9d9e7daacd0cb79d4ef75c64a2af316e68447f0df0/regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d", size = 291122, upload-time = "2026-01-14T23:16:55.383Z" },
{ url = "https://files.pythonhosted.org/packages/cf/55/bb8ccbacabbc3a11d863ee62a9f18b160a83084ea95cdfc5d207bfc3dd75/regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84", size = 807761, upload-time = "2026-01-14T23:16:57.251Z" },
{ url = "https://files.pythonhosted.org/packages/8f/84/f75d937f17f81e55679a0509e86176e29caa7298c38bd1db7ce9c0bf6075/regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df", size = 873538, upload-time = "2026-01-14T23:16:59.349Z" },
{ url = "https://files.pythonhosted.org/packages/b8/d9/0da86327df70349aa8d86390da91171bd3ca4f0e7c1d1d453a9c10344da3/regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434", size = 915066, upload-time = "2026-01-14T23:17:01.607Z" },
{ url = "https://files.pythonhosted.org/packages/2a/5e/f660fb23fc77baa2a61aa1f1fe3a4eea2bbb8a286ddec148030672e18834/regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a", size = 812938, upload-time = "2026-01-14T23:17:04.366Z" },
{ url = "https://files.pythonhosted.org/packages/69/33/a47a29bfecebbbfd1e5cd3f26b28020a97e4820f1c5148e66e3b7d4b4992/regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10", size = 781314, upload-time = "2026-01-14T23:17:06.378Z" },
{ url = "https://files.pythonhosted.org/packages/65/ec/7ec2bbfd4c3f4e494a24dec4c6943a668e2030426b1b8b949a6462d2c17b/regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac", size = 795652, upload-time = "2026-01-14T23:17:08.521Z" },
{ url = "https://files.pythonhosted.org/packages/46/79/a5d8651ae131fe27d7c521ad300aa7f1c7be1dbeee4d446498af5411b8a9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea", size = 868550, upload-time = "2026-01-14T23:17:10.573Z" },
{ url = "https://files.pythonhosted.org/packages/06/b7/25635d2809664b79f183070786a5552dd4e627e5aedb0065f4e3cf8ee37d/regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e", size = 769981, upload-time = "2026-01-14T23:17:12.871Z" },
{ url = "https://files.pythonhosted.org/packages/16/8b/fc3fcbb2393dcfa4a6c5ffad92dc498e842df4581ea9d14309fcd3c55fb9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521", size = 854780, upload-time = "2026-01-14T23:17:14.837Z" },
{ url = "https://files.pythonhosted.org/packages/d0/38/dde117c76c624713c8a2842530be9c93ca8b606c0f6102d86e8cd1ce8bea/regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db", size = 799778, upload-time = "2026-01-14T23:17:17.369Z" },
{ url = "https://files.pythonhosted.org/packages/e3/0d/3a6cfa9ae99606afb612d8fb7a66b245a9d5ff0f29bb347c8a30b6ad561b/regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e", size = 274667, upload-time = "2026-01-14T23:17:19.301Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b2/297293bb0742fd06b8d8e2572db41a855cdf1cae0bf009b1cb74fe07e196/regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf", size = 284386, upload-time = "2026-01-14T23:17:21.231Z" },
{ url = "https://files.pythonhosted.org/packages/95/e4/a3b9480c78cf8ee86626cb06f8d931d74d775897d44201ccb813097ae697/regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70", size = 274837, upload-time = "2026-01-14T23:17:23.146Z" },
]
[[package]]
@@ -508,153 +571,153 @@ sdist = { url = "https://files.pythonhosted.org/packages/05/35/298d9410b3635107c
[[package]]
name = "ruff"
version = "0.14.5"
version = "0.15.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" }
sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" },
{ url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925, upload-time = "2025-11-13T19:57:59.181Z" },
{ url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040, upload-time = "2025-11-13T19:58:02.056Z" },
{ url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755, upload-time = "2025-11-13T19:58:05.172Z" },
{ url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641, upload-time = "2025-11-13T19:58:08.345Z" },
{ url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854, upload-time = "2025-11-13T19:58:11.419Z" },
{ url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088, upload-time = "2025-11-13T19:58:14.551Z" },
{ url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717, upload-time = "2025-11-13T19:58:17.518Z" },
{ url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812, upload-time = "2025-11-13T19:58:20.533Z" },
{ url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656, upload-time = "2025-11-13T19:58:23.345Z" },
{ url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922, upload-time = "2025-11-13T19:58:26.537Z" },
{ url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501, upload-time = "2025-11-13T19:58:29.822Z" },
{ url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319, upload-time = "2025-11-13T19:58:32.923Z" },
{ url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209, upload-time = "2025-11-13T19:58:35.952Z" },
{ url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709, upload-time = "2025-11-13T19:58:39.002Z" },
{ url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808, upload-time = "2025-11-13T19:58:42.779Z" },
{ url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546, upload-time = "2025-11-13T19:58:45.691Z" },
{ url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" },
{ url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" },
{ url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" },
{ url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" },
{ url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" },
{ url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" },
{ url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" },
{ url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" },
{ url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" },
{ url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" },
{ url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" },
{ url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" },
{ url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" },
{ url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" },
{ url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" },
{ url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" },
{ url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" },
{ url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" },
]
[[package]]
name = "soupsieve"
version = "2.8"
version = "2.8.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" },
{ url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" },
]
[[package]]
name = "tdewolff-minify"
version = "2.24.7"
version = "2.24.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/6d/0bfb7ac55546690fdb5c3f8fa5d43e1618bff4e688bbd8ebcc58487f260d/tdewolff_minify-2.24.7.tar.gz", hash = "sha256:63b7066a67c6f117934fc64dd3609a6fb5f8f9186b385343efaeb6693aded627", size = 3694070, upload-time = "2025-11-08T08:56:28.217Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/aa/69091c2f8499bb95e0b46578e71eaf5292e9b21637a9df12057efd285624/tdewolff_minify-2.24.8.tar.gz", hash = "sha256:1f4163f6bb8fdfb27438d531fc63933cc535508f4a57c7f9217888d786ad763e", size = 3694071, upload-time = "2025-12-08T13:08:03.963Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/0a/adc88fdb484ca4b710a34117227fd0e22df6a65e87f58f581a9cad5e0c62/tdewolff_minify-2.24.7-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:34227ec8a78f4cfc76c7b5c6879a3c680953923fbab0d83a2d117140cd40f093", size = 2084470, upload-time = "2025-11-08T08:55:58.327Z" },
{ url = "https://files.pythonhosted.org/packages/96/6f/bf20d2d9b856a19f9a359caf4882aa9a8cab82659d45819f8805e5047ee8/tdewolff_minify-2.24.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19f79615af84b9d465cb8f881b2b4d0054d9c69416d752f78f59d36acbe72e7a", size = 3902309, upload-time = "2025-11-08T08:55:59.768Z" },
{ url = "https://files.pythonhosted.org/packages/12/2b/0d27d777a3f0e3c7232def747fbb404390956debae69a4ede46b723cf636/tdewolff_minify-2.24.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:795ee7c22a4c2860f6687de8a77bead70ca944207f39a9675b5122f441f0e860", size = 3620150, upload-time = "2025-11-08T08:56:00.946Z" },
{ url = "https://files.pythonhosted.org/packages/01/c8/8b000b99f15e564cb940f5179e1481b7a3b88483bde650999dd28c42829f/tdewolff_minify-2.24.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2cc97500de2e85734f1f2ecaeba4dfb4e413e1c029c196d0657098db75336ddf", size = 3438232, upload-time = "2025-11-08T08:56:02.479Z" },
{ url = "https://files.pythonhosted.org/packages/d7/0e/864c5e51c26e78564a25d62aedfde83f5f16f2fe08337fd3ad7fd95af0b8/tdewolff_minify-2.24.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:097bb7bb110b8d47224696479d90068428a859b46e0369649fe61340748a8ecc", size = 3715723, upload-time = "2025-11-08T08:56:03.659Z" },
{ url = "https://files.pythonhosted.org/packages/a9/9f/6d31aefede6a0cc0f484dbaca4d2770ec9253a0a1e33a2267ee4ea566a4e/tdewolff_minify-2.24.7-cp314-cp314-win_amd64.whl", hash = "sha256:c033ba059995246626debba0843bf87b2dd167c989f02168e94611bb694f0093", size = 3716828, upload-time = "2025-11-08T08:56:04.879Z" },
{ url = "https://files.pythonhosted.org/packages/3e/00/664f2279f0d8bcd37c9893aa16a967b1bde40fc62264dad4421aea086d49/tdewolff_minify-2.24.7-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7db0bbc94d483f5d59fea87860d9c0a7691cc361df66e8a137bc1d57c69e5507", size = 2084472, upload-time = "2025-11-08T08:56:06.092Z" },
{ url = "https://files.pythonhosted.org/packages/79/5d/c88f400623e5ed8d00a34d3f4958a51502c5c80127fdd9c2e54729e10d03/tdewolff_minify-2.24.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:385ccc7df026dcc6057785b9ec0cd7ebf27b1df5afd87a3899da165b3aa2a7ef", size = 3902309, upload-time = "2025-11-08T08:56:07.26Z" },
{ url = "https://files.pythonhosted.org/packages/88/ef/b6ff62120a0de435aea4440be390501ca43021fcdeec2a5efb49a6c9a856/tdewolff_minify-2.24.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6965e983e351bc73445025d45f874165683403b74611560458e7fdcf2064ed9", size = 3620152, upload-time = "2025-11-08T08:56:08.386Z" },
{ url = "https://files.pythonhosted.org/packages/63/4d/d2cb974ca67af741736377c62c57c3466ce223005e0caa36377fd87a9052/tdewolff_minify-2.24.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fed2c8794b12aa950a61307e41837e9e1ca35820158b8bb196666ad7570850bf", size = 3438233, upload-time = "2025-11-08T08:56:09.589Z" },
{ url = "https://files.pythonhosted.org/packages/ad/e8/a04c25a5801349e53e2df143c1f1a77703f8dabeb5a6053c304e59d13be7/tdewolff_minify-2.24.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d68fd60d94fcc7ccec2d3b734120c0a38c85f24b696e6cae7c4081919770cea", size = 3715725, upload-time = "2025-11-08T08:56:10.806Z" },
{ url = "https://files.pythonhosted.org/packages/93/fb/d0c03ee765639ff4c4ba06b247b56edb8314623fe7efb3f06d7e1c3a2a33/tdewolff_minify-2.24.7-cp314-cp314t-win_amd64.whl", hash = "sha256:7338637f1c7b7a2f3d6b354ecae04e4dcf76e3b2e4089f38192efc0793d280dc", size = 3716829, upload-time = "2025-11-08T08:56:12.352Z" },
{ url = "https://files.pythonhosted.org/packages/09/f0/bf9c33414175226e1d92d478350f59f2e0ff5ff273d45d73b9bafd4dda3d/tdewolff_minify-2.24.8-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9eaf43b301a3915956dae933a2671927bdad371987beb6550b464aa776f4ddb", size = 2084437, upload-time = "2025-12-08T13:07:29.512Z" },
{ url = "https://files.pythonhosted.org/packages/a1/b3/f6b322cdf6f64d35f1ad8bb227da9eb7070ba4256192705eac0cc9b36a53/tdewolff_minify-2.24.8-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:287769c6c88bc70d8929fd68d9e74f90fe08f2b767bbad8eaecc0dbea21a4436", size = 3977290, upload-time = "2025-12-08T13:07:31.16Z" },
{ url = "https://files.pythonhosted.org/packages/a8/a4/371a54b28b8f9d18c2a9b0edccc8c0693885f2881e6966c4066bf05dabbd/tdewolff_minify-2.24.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e9637198c46f8105a16b982c56a478be35e956652a62335a20aa1345c2edfe2", size = 3651688, upload-time = "2025-12-08T13:07:32.67Z" },
{ url = "https://files.pythonhosted.org/packages/96/c3/67baa7c0230c5224c93eeca3dab3a57a2c099d64243908369e5f1212d541/tdewolff_minify-2.24.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d5db2b229a84889bb2362aa02c44a99010d3ec4913461c1cc55473c5a83f1ac0", size = 3437790, upload-time = "2025-12-08T13:07:34.085Z" },
{ url = "https://files.pythonhosted.org/packages/6f/57/c6face693639ebdc730e39a53cbb0c3ebea23ad0c2015cc0e9b05a302780/tdewolff_minify-2.24.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0319f803cd26b5b6d1956fdc0c2a2b723377fc9d4bcc7a746c555e7121d79d51", size = 3715504, upload-time = "2025-12-08T13:07:35.761Z" },
{ url = "https://files.pythonhosted.org/packages/15/5a/27b7e1f515ce421e64d3e181246a7442f5e3a728df7ecbd2059cd7d95e76/tdewolff_minify-2.24.8-cp314-cp314-win_amd64.whl", hash = "sha256:4af0d853e860a312707d257058965987eb61e1957c342fb0596b6dbadd256add", size = 3716533, upload-time = "2025-12-08T13:07:37.142Z" },
{ url = "https://files.pythonhosted.org/packages/88/2c/6506f074b4ba4fd5775c7efb445d624e8af11ec1485281d2c72f7e824d4f/tdewolff_minify-2.24.8-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dd956508971c1e37416afad5ff6d13d5afb4163b6b2a4c1be8be887b13ee7944", size = 2084438, upload-time = "2025-12-08T13:07:38.761Z" },
{ url = "https://files.pythonhosted.org/packages/99/50/03d71acd6c9cca5bd0ad41c0bd2ed566218b7ca4aca1a9bb445ff8b65aae/tdewolff_minify-2.24.8-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cc27b832fc8e2e2b9db9f30f2250fc16b8b57643854d10864580f317dc496b9f", size = 3977291, upload-time = "2025-12-08T13:07:40.02Z" },
{ url = "https://files.pythonhosted.org/packages/df/8c/06cff11585cb697fa7015be01313329f902a69a63f0f4ecfec834eb807c5/tdewolff_minify-2.24.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efd02e17985b8ae15173387b50ff4d7d87f811c281be9cd3dc454164e02058f9", size = 3651688, upload-time = "2025-12-08T13:07:41.7Z" },
{ url = "https://files.pythonhosted.org/packages/37/54/97c1439deda48a728b8538414af9c1deb3e290c383ee614d720ac568f450/tdewolff_minify-2.24.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49acc327d36eb654973bc0035b187fa44ad89cd0fc3912624315882b56391faf", size = 3437789, upload-time = "2025-12-08T13:07:43.112Z" },
{ url = "https://files.pythonhosted.org/packages/bb/82/7e7bdc2c3c85081a5b30835c7cdca38f304762957093dda11a05a147381c/tdewolff_minify-2.24.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d922d51ea91222f76e52015c02b73b67fd2ae9f6b67f06047d47086dd67f27b6", size = 3715505, upload-time = "2025-12-08T13:07:44.501Z" },
{ url = "https://files.pythonhosted.org/packages/2f/21/f923f8079f7d53c78ad6fc87b9bf4c6aef88802e357c099474f9564e1838/tdewolff_minify-2.24.8-cp314-cp314t-win_amd64.whl", hash = "sha256:6d166afc3444737dfaa20ee16f9526b3f81ab689757f49bf23933f33ed8d93a2", size = 3716535, upload-time = "2025-12-08T13:07:45.873Z" },
]
[[package]]
name = "tombi"
version = "0.7.11"
version = "0.7.31"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d9/df/2d28eef46563798d4ca17f0d0826b94e8eb430edef15f5b7efc3b44aadec/tombi-0.7.11.tar.gz", hash = "sha256:82d1c41d34da3a6a7c4e11b181edc26fb2fd13bf76ea5f3447a53a0444fbc091", size = 472046, upload-time = "2025-12-25T16:14:59.71Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ea/e6/f32e480e8b164b3439bd31c73b3d87a18bc5e3c3d02ac7f5d352266befc7/tombi-0.7.31.tar.gz", hash = "sha256:5332a7462568daf344c97a0c60cc35e51f2a5cf8e697b251db5e4371eb134ff6", size = 493144, upload-time = "2026-02-17T17:10:45.412Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/47/f077bf798d6af40c2669b6a9533978993193d51bca75613959679fe76c73/tombi-0.7.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f0e6305c12f8a5e471a9159064bacbab4fd93de328b5db6c828aa7da15ca2acc", size = 9064434, upload-time = "2025-12-25T16:14:50.292Z" },
{ url = "https://files.pythonhosted.org/packages/7a/d9/290d506d6685849ccd6012f4253de1bb10037def31925cf6c182da42a1c2/tombi-0.7.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:65475db4d1f68c3bfa8e5119db733071184489fb9120d666e8d6f0cad821f087", size = 8770616, upload-time = "2025-12-25T16:14:48.444Z" },
{ url = "https://files.pythonhosted.org/packages/f4/87/cf0268e9986241fb867b7269fb77a2429987dccf7e2843e7c47f044de6dd/tombi-0.7.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69d55708c1ccf651ae626bad6a5ba0e7f6b5102373905e48ffc76a7fdf37cfab", size = 8971843, upload-time = "2025-12-25T16:14:37.976Z" },
{ url = "https://files.pythonhosted.org/packages/9c/73/772385c3fbf5598cc1521716ef6f3d71eac54fcffd1d67c8d1553f3e903b/tombi-0.7.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f8243d31259804009a1960ee3e8cdf5aec3151a6625be5109937b118c8d36d2", size = 10098451, upload-time = "2025-12-25T16:14:44.093Z" },
{ url = "https://files.pythonhosted.org/packages/99/54/00c278fecb341fff0b08e4c24b0f9af59356b9cefe79e784842e9c187ff5/tombi-0.7.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1307059a1cbed9a9a4add2f6a340986156aeee3830b9c6686ba5b88baabdfb6c", size = 10197359, upload-time = "2025-12-25T16:14:39.871Z" },
{ url = "https://files.pythonhosted.org/packages/e6/11/d47663b128497a9b290f78d49925d1e2b6d8117d49148dbf15c50850a316/tombi-0.7.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b42adcc15025e81818686313d830b0ef47c505f0451768574ddd1e4198a63724", size = 9108951, upload-time = "2025-12-25T16:14:41.969Z" },
{ url = "https://files.pythonhosted.org/packages/44/ff/6b85c162e27de3232e8548ef18fd49a87da43886cad28f48358d8004959e/tombi-0.7.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55fc218065d33b34cf1dbcafb0d5e9be0dcc5a346a00df420130cadb36c18029", size = 9405453, upload-time = "2025-12-25T16:14:46.309Z" },
{ url = "https://files.pythonhosted.org/packages/5f/91/f59cb04601ac6edcb63a2e6c3f98c788adddf6893e2f788b7029acc29fa7/tombi-0.7.11-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:234a1624959d6adb89e10cad7847e6270a28c40fb1f3c0444c7f2e9519a5de49", size = 9245316, upload-time = "2025-12-25T16:14:35.679Z" },
{ url = "https://files.pythonhosted.org/packages/1a/98/ac113e7aa01b89150ab689555b56f7d70c169156e153dfab6095d09cc2b5/tombi-0.7.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e05862a896913de66ffb152c354d793376a1ab74c2241c2fb7255820b2a081b9", size = 9266952, upload-time = "2025-12-25T16:14:52.237Z" },
{ url = "https://files.pythonhosted.org/packages/7c/14/33cbfc9d3ba013e74453dfd6cace2cf1d118161ded758325c4ecd46b58e3/tombi-0.7.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bdfbf018b6f7bd39a3f572d98ced85d77b91b86736623d728b271f168165ac45", size = 8992879, upload-time = "2025-12-25T16:14:54.369Z" },
{ url = "https://files.pythonhosted.org/packages/88/e0/91aaaa7435a8d5cab49e10066be37b908016a38dd7ecb89398d92907dec5/tombi-0.7.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f295d870c7f6f2e88bcce69a4cb527a3f821f55b42e010c5e1fd81e3c7eed253", size = 9570488, upload-time = "2025-12-25T16:14:56.361Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ec/dbe8c4d937a82a0d04a44b8e34b05ce8bfc489fec04f4489e5562292705b/tombi-0.7.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:510daab10c7f77329cb2c1c6c64a3692ddf4e04746a08e9f642d19a4b5000f68", size = 9633925, upload-time = "2025-12-25T16:14:58.138Z" },
{ url = "https://files.pythonhosted.org/packages/26/8a/c2c8205b0cde7472df2b7377eb853edf6f8d2f2320478059d383e0ca92ac/tombi-0.7.11-py3-none-win32.whl", hash = "sha256:f1f7f35dc60302adf64a3e16b7125f8d56a4c445a37e71d58d363cc45a30cb1d", size = 7142499, upload-time = "2025-12-25T16:15:03.28Z" },
{ url = "https://files.pythonhosted.org/packages/6c/86/d1762e54e3372713efaf18318a90673f810567e5c7a3e7936e74485be1ab/tombi-0.7.11-py3-none-win_amd64.whl", hash = "sha256:50a969d00d0fc4e85d84e01353afa603014550dc2e56a5a502f4ebd614bb03b2", size = 8326898, upload-time = "2025-12-25T16:15:01.276Z" },
{ url = "https://files.pythonhosted.org/packages/03/4d/2242fba01383cd6d92f350f0277e4d89e1ff2dc87d23dcf8ff7d0839ae0b/tombi-0.7.31-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8dc17f29fe752e21d6c4c3eddff3b34ff765111633483850d0c5edd7c03ca854", size = 8886445, upload-time = "2026-02-17T17:10:36.991Z" },
{ url = "https://files.pythonhosted.org/packages/92/48/ec6a8e3d6884926833fc20703088fb1915fd0385fe6340cc40547e81faca/tombi-0.7.31-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a42e3a1625e966c4eef01293d3243730c041059b9073816968b62d57b586130f", size = 8609324, upload-time = "2026-02-17T17:10:35.237Z" },
{ url = "https://files.pythonhosted.org/packages/e2/12/989502e3b01620ed5e267456d91e7eb1fa678232273c57205db625b6453f/tombi-0.7.31-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d29e2e497a53ab961ddaef125a3bea8b67f55605a5256a1f7c5846594cba5e52", size = 8817624, upload-time = "2026-02-17T17:10:25.846Z" },
{ url = "https://files.pythonhosted.org/packages/46/27/1e25d654cfc7ba59b13d08ccc4f6bd5253da23fefb7e9eb7e2560caa9841/tombi-0.7.31-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55bc8abfe7fcb01974a0f6911dddd948f98bf98221cb8eba426bd4fe362af91f", size = 9952445, upload-time = "2026-02-17T17:10:31.38Z" },
{ url = "https://files.pythonhosted.org/packages/6e/9e/ff6d710505afc5ea873178d92a7cc4a50a89eb548a48718dfad6c460ba5f/tombi-0.7.31-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a46f066e1f0ca63f723a83b52dbdfec8fcd21fad23eb4d76d24192da4975967c", size = 10068931, upload-time = "2026-02-17T17:10:27.519Z" },
{ url = "https://files.pythonhosted.org/packages/95/91/bece25790fc299376255babf527063b6d33821511986135c1060c36babc5/tombi-0.7.31-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86131c017ce128308b22a41923217c3b27dfe8cf6ea6a7c78045aab82b49d3cc", size = 8987347, upload-time = "2026-02-17T17:10:29.287Z" },
{ url = "https://files.pythonhosted.org/packages/a2/29/3fd9e83e1c2e28f019199ff42c5c14750a82190863f391536aa7681b76a0/tombi-0.7.31-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54ee818268d67cf0ec6a1148abaffcd64e217f9d4ddc1bbbcbaeca2f3596775a", size = 9255182, upload-time = "2026-02-17T17:10:33.585Z" },
{ url = "https://files.pythonhosted.org/packages/66/90/ef4f2347f9486c9968ddcb5a79ce2aad38f3d0ee01db9e90e8d82684fe92/tombi-0.7.31-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:1d12b33f7eea8404d1448f5f29d2b3765fed6a819e379c81430a219d671471ee", size = 9080726, upload-time = "2026-02-17T17:10:23.715Z" },
{ url = "https://files.pythonhosted.org/packages/21/cf/1740726f506fc1cb1773b038b07a43f9c07c43196f0abeaa04ae9c093b38/tombi-0.7.31-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d33efd3aa9b5b404cee597c92cc6e4e9c7492cf5353ca965cfe4277e99bfc3d3", size = 9117347, upload-time = "2026-02-17T17:10:38.663Z" },
{ url = "https://files.pythonhosted.org/packages/5b/62/d0eb5a80726aa53223535ea9b5a01a89ac36611d74dbf1e6563266ca1d2d/tombi-0.7.31-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ecaa051f3424e4571e8489cc08ca075654310d20eed6bd209ba040014096b588", size = 8863022, upload-time = "2026-02-17T17:10:40.472Z" },
{ url = "https://files.pythonhosted.org/packages/8d/09/ccadf98ad7ef3c24f70ffe27b59e5423c9f2f712eac172679abdb425ee4a/tombi-0.7.31-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b54c28e321f8dbfee2561d224159d35285edcfcc7f4c83772c1b6314a826fc2", size = 9425178, upload-time = "2026-02-17T17:10:42.125Z" },
{ url = "https://files.pythonhosted.org/packages/26/b3/6b459b1ae8a78eb4e0acfb9dabdcda9a5e6463d4272b3becbc9378e9af2f/tombi-0.7.31-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8f44cba5198e445a629e6c525b1a5ba32bb283d357e25ac2aaf5d997e0d10f40", size = 9461979, upload-time = "2026-02-17T17:10:43.964Z" },
{ url = "https://files.pythonhosted.org/packages/0b/d3/23dd2fbde00368707a16d6587be4581b2cd979624858440b2d762e5471df/tombi-0.7.31-py3-none-win32.whl", hash = "sha256:c740e03ee70018edf948447c4b0b44b2ac57d148b7b6355ef258902eb6563ebe", size = 7024683, upload-time = "2026-02-17T17:10:48.648Z" },
{ url = "https://files.pythonhosted.org/packages/13/27/acafcf84790b85286b1563bab3d2026aba35a1894a4b98c21d041f8005c2/tombi-0.7.31-py3-none-win_amd64.whl", hash = "sha256:5af24999a185bb1d003dc28429ad4549e65aa986b92410a35ffa4306233116d3", size = 8171473, upload-time = "2026-02-17T17:10:46.479Z" },
]
[[package]]
name = "tomlkit"
version = "0.13.3"
version = "0.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
{ url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" },
]
[[package]]
name = "tqdm"
version = "4.67.1"
version = "4.67.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
]
[[package]]
name = "ty"
version = "0.0.14"
version = "0.0.17"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/57/22c3d6bf95c2229120c49ffc2f0da8d9e8823755a1c3194da56e51f1cc31/ty-0.0.14.tar.gz", hash = "sha256:a691010565f59dd7f15cf324cdcd1d9065e010c77a04f887e1ea070ba34a7de2", size = 5036573, upload-time = "2026-01-27T00:57:31.427Z" }
sdist = { url = "https://files.pythonhosted.org/packages/66/c3/41ae6346443eedb65b96761abfab890a48ce2aa5a8a27af69c5c5d99064d/ty-0.0.17.tar.gz", hash = "sha256:847ed6c120913e280bf9b54d8eaa7a1049708acb8824ad234e71498e8ad09f97", size = 5167209, upload-time = "2026-02-13T13:26:36.835Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/99/cb/cc6d1d8de59beb17a41f9a614585f884ec2d95450306c173b3b7cc090d2e/ty-0.0.14-py3-none-linux_armv6l.whl", hash = "sha256:32cf2a7596e693094621d3ae568d7ee16707dce28c34d1762947874060fdddaa", size = 10034228, upload-time = "2026-01-27T00:57:53.133Z" },
{ url = "https://files.pythonhosted.org/packages/f3/96/dd42816a2075a8f31542296ae687483a8d047f86a6538dfba573223eaf9a/ty-0.0.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f971bf9805f49ce8c0968ad53e29624d80b970b9eb597b7cbaba25d8a18ce9a2", size = 9939162, upload-time = "2026-01-27T00:57:43.857Z" },
{ url = "https://files.pythonhosted.org/packages/ff/b4/73c4859004e0f0a9eead9ecb67021438b2e8e5fdd8d03e7f5aca77623992/ty-0.0.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:45448b9e4806423523268bc15e9208c4f3f2ead7c344f615549d2e2354d6e924", size = 9418661, upload-time = "2026-01-27T00:58:03.411Z" },
{ url = "https://files.pythonhosted.org/packages/58/35/839c4551b94613db4afa20ee555dd4f33bfa7352d5da74c5fa416ffa0fd2/ty-0.0.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee94a9b747ff40114085206bdb3205a631ef19a4d3fb89e302a88754cbbae54c", size = 9837872, upload-time = "2026-01-27T00:57:23.718Z" },
{ url = "https://files.pythonhosted.org/packages/41/2b/bbecf7e2faa20c04bebd35fc478668953ca50ee5847ce23e08acf20ea119/ty-0.0.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6756715a3c33182e9ab8ffca2bb314d3c99b9c410b171736e145773ee0ae41c3", size = 9848819, upload-time = "2026-01-27T00:57:58.501Z" },
{ url = "https://files.pythonhosted.org/packages/be/60/3c0ba0f19c0f647ad9d2b5b5ac68c0f0b4dc899001bd53b3a7537fb247a2/ty-0.0.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89d0038a2f698ba8b6fec5cf216a4e44e2f95e4a5095a8c0f57fe549f87087c2", size = 10324371, upload-time = "2026-01-27T00:57:29.291Z" },
{ url = "https://files.pythonhosted.org/packages/24/32/99d0a0b37d0397b0a989ffc2682493286aa3bc252b24004a6714368c2c3d/ty-0.0.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c64a83a2d669b77f50a4957039ca1450626fb474619f18f6f8a3eb885bf7544", size = 10865898, upload-time = "2026-01-27T00:57:33.542Z" },
{ url = "https://files.pythonhosted.org/packages/1a/88/30b583a9e0311bb474269cfa91db53350557ebec09002bfc3fb3fc364e8c/ty-0.0.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:242488bfb547ef080199f6fd81369ab9cb638a778bb161511d091ffd49c12129", size = 10555777, upload-time = "2026-01-27T00:58:05.853Z" },
{ url = "https://files.pythonhosted.org/packages/cd/a2/cb53fb6325dcf3d40f2b1d0457a25d55bfbae633c8e337bde8ec01a190eb/ty-0.0.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4790c3866f6c83a4f424fc7d09ebdb225c1f1131647ba8bdc6fcdc28f09ed0ff", size = 10412913, upload-time = "2026-01-27T00:57:38.834Z" },
{ url = "https://files.pythonhosted.org/packages/42/8f/f2f5202d725ed1e6a4e5ffaa32b190a1fe70c0b1a2503d38515da4130b4c/ty-0.0.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:950f320437f96d4ea9a2332bbfb5b68f1c1acd269ebfa4c09b6970cc1565bd9d", size = 9837608, upload-time = "2026-01-27T00:57:55.898Z" },
{ url = "https://files.pythonhosted.org/packages/f7/ba/59a2a0521640c489dafa2c546ae1f8465f92956fede18660653cce73b4c5/ty-0.0.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a0ec3ee70d83887f86925bbc1c56f4628bd58a0f47f6f32ddfe04e1f05466df", size = 9884324, upload-time = "2026-01-27T00:57:46.786Z" },
{ url = "https://files.pythonhosted.org/packages/03/95/8d2a49880f47b638743212f011088552ecc454dd7a665ddcbdabea25772a/ty-0.0.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1a4e6b6da0c58b34415955279eff754d6206b35af56a18bb70eb519d8d139ef", size = 10033537, upload-time = "2026-01-27T00:58:01.149Z" },
{ url = "https://files.pythonhosted.org/packages/e9/40/4523b36f2ce69f92ccf783855a9e0ebbbd0f0bb5cdce6211ee1737159ed3/ty-0.0.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dc04384e874c5de4c5d743369c277c8aa73d1edea3c7fc646b2064b637db4db3", size = 10495910, upload-time = "2026-01-27T00:57:26.691Z" },
{ url = "https://files.pythonhosted.org/packages/08/d5/655beb51224d1bfd4f9ddc0bb209659bfe71ff141bcf05c418ab670698f0/ty-0.0.14-py3-none-win32.whl", hash = "sha256:b20e22cf54c66b3e37e87377635da412d9a552c9bf4ad9fc449fed8b2e19dad2", size = 9507626, upload-time = "2026-01-27T00:57:41.43Z" },
{ url = "https://files.pythonhosted.org/packages/b6/d9/c569c9961760e20e0a4bc008eeb1415754564304fd53997a371b7cf3f864/ty-0.0.14-py3-none-win_amd64.whl", hash = "sha256:e312ff9475522d1a33186657fe74d1ec98e4a13e016d66f5758a452c90ff6409", size = 10437980, upload-time = "2026-01-27T00:57:36.422Z" },
{ url = "https://files.pythonhosted.org/packages/ad/0c/186829654f5bfd9a028f6648e9caeb11271960a61de97484627d24443f91/ty-0.0.14-py3-none-win_arm64.whl", hash = "sha256:b6facdbe9b740cb2c15293a1d178e22ffc600653646452632541d01c36d5e378", size = 9885831, upload-time = "2026-01-27T00:57:49.747Z" },
{ url = "https://files.pythonhosted.org/packages/c0/01/0ef15c22a1c54b0f728ceff3f62d478dbf8b0dcf8ff7b80b954f79584f3e/ty-0.0.17-py3-none-linux_armv6l.whl", hash = "sha256:64a9a16555cc8867d35c2647c2f1afbd3cae55f68fd95283a574d1bb04fe93e0", size = 10192793, upload-time = "2026-02-13T13:27:13.943Z" },
{ url = "https://files.pythonhosted.org/packages/0f/2c/f4c322d9cded56edc016b1092c14b95cf58c8a33b4787316ea752bb9418e/ty-0.0.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eb2dbd8acd5c5a55f4af0d479523e7c7265a88542efe73ed3d696eb1ba7b6454", size = 10051977, upload-time = "2026-02-13T13:26:57.741Z" },
{ url = "https://files.pythonhosted.org/packages/4c/a5/43746c1ff81e784f5fc303afc61fe5bcd85d0fcf3ef65cb2cef78c7486c7/ty-0.0.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f18f5fd927bc628deb9ea2df40f06b5f79c5ccf355db732025a3e8e7152801f6", size = 9564639, upload-time = "2026-02-13T13:26:42.781Z" },
{ url = "https://files.pythonhosted.org/packages/d6/b8/280b04e14a9c0474af574f929fba2398b5e1c123c1e7735893b4cd73d13c/ty-0.0.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5383814d1d7a5cc53b3b07661856bab04bb2aac7a677c8d33c55169acdaa83df", size = 10061204, upload-time = "2026-02-13T13:27:00.152Z" },
{ url = "https://files.pythonhosted.org/packages/2a/d7/493e1607d8dfe48288d8a768a2adc38ee27ef50e57f0af41ff273987cda0/ty-0.0.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c20423b8744b484f93e7bf2ef8a9724bca2657873593f9f41d08bd9f83444c9", size = 10013116, upload-time = "2026-02-13T13:26:34.543Z" },
{ url = "https://files.pythonhosted.org/packages/80/ef/22f3ed401520afac90dbdf1f9b8b7755d85b0d5c35c1cb35cf5bd11b59c2/ty-0.0.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6f5b1aba97db9af86517b911674b02f5bc310750485dc47603a105bd0e83ddd", size = 10533623, upload-time = "2026-02-13T13:26:31.449Z" },
{ url = "https://files.pythonhosted.org/packages/75/ce/744b15279a11ac7138832e3a55595706b4a8a209c9f878e3ab8e571d9032/ty-0.0.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:488bce1a9bea80b851a97cd34c4d2ffcd69593d6c3f54a72ae02e5c6e47f3d0c", size = 11069750, upload-time = "2026-02-13T13:26:48.638Z" },
{ url = "https://files.pythonhosted.org/packages/f2/be/1133c91f15a0e00d466c24f80df486d630d95d1b2af63296941f7473812f/ty-0.0.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8df66b91ec84239420985ec215e7f7549bfda2ac036a3b3c065f119d1c06825a", size = 10870862, upload-time = "2026-02-13T13:26:54.715Z" },
{ url = "https://files.pythonhosted.org/packages/3e/4a/a2ed209ef215b62b2d3246e07e833081e07d913adf7e0448fc204be443d6/ty-0.0.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:002139e807c53002790dfefe6e2f45ab0e04012e76db3d7c8286f96ec121af8f", size = 10628118, upload-time = "2026-02-13T13:26:45.439Z" },
{ url = "https://files.pythonhosted.org/packages/b3/0c/87476004cb5228e9719b98afffad82c3ef1f84334bde8527bcacba7b18cb/ty-0.0.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6c4e01f05ce82e5d489ab3900ca0899a56c4ccb52659453780c83e5b19e2b64c", size = 10038185, upload-time = "2026-02-13T13:27:02.693Z" },
{ url = "https://files.pythonhosted.org/packages/46/4b/98f0b3ba9aef53c1f0305519536967a4aa793a69ed72677b0a625c5313ac/ty-0.0.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2b226dd1e99c0d2152d218c7e440150d1a47ce3c431871f0efa073bbf899e881", size = 10047644, upload-time = "2026-02-13T13:27:05.474Z" },
{ url = "https://files.pythonhosted.org/packages/93/e0/06737bb80aa1a9103b8651d2eb691a7e53f1ed54111152be25f4a02745db/ty-0.0.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8b11f1da7859e0ad69e84b3c5ef9a7b055ceed376a432fad44231bdfc48061c2", size = 10231140, upload-time = "2026-02-13T13:27:10.844Z" },
{ url = "https://files.pythonhosted.org/packages/7c/79/e2a606bd8852383ba9abfdd578f4a227bd18504145381a10a5f886b4e751/ty-0.0.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c04e196809ff570559054d3e011425fd7c04161529eb551b3625654e5f2434cb", size = 10718344, upload-time = "2026-02-13T13:26:51.66Z" },
{ url = "https://files.pythonhosted.org/packages/c5/2d/2663984ac11de6d78f74432b8b14ba64d170b45194312852b7543cf7fd56/ty-0.0.17-py3-none-win32.whl", hash = "sha256:305b6ed150b2740d00a817b193373d21f0767e10f94ac47abfc3b2e5a5aec809", size = 9672932, upload-time = "2026-02-13T13:27:08.522Z" },
{ url = "https://files.pythonhosted.org/packages/de/b5/39be78f30b31ee9f5a585969930c7248354db90494ff5e3d0756560fb731/ty-0.0.17-py3-none-win_amd64.whl", hash = "sha256:531828267527aee7a63e972f54e5eee21d9281b72baf18e5c2850c6b862add83", size = 10542138, upload-time = "2026-02-13T13:27:17.084Z" },
{ url = "https://files.pythonhosted.org/packages/40/b7/f875c729c5d0079640c75bad2c7e5d43edc90f16ba242f28a11966df8f65/ty-0.0.17-py3-none-win_arm64.whl", hash = "sha256:de9810234c0c8d75073457e10a84825b9cd72e6629826b7f01c7a0b266ae25b1", size = 10023068, upload-time = "2026-02-13T13:26:39.637Z" },
]
[[package]]
name = "types-html5lib"
version = "1.1.11.20251115"
version = "1.1.11.20251117"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "types-webencodings" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/09/750cc27de71af3fd9eac061fd23e472730b49d8f06eb7d128a48e41549c7/types_html5lib-1.1.11.20251115.tar.gz", hash = "sha256:a4b666a06e496d7b2a9489dc9206f09a249fab7c62865ac6024cec24628b15d3", size = 17934, upload-time = "2025-11-15T03:00:17.107Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c8/f3/d9a1bbba7b42b5558a3f9fe017d967f5338cf8108d35991d9b15fdea3e0d/types_html5lib-1.1.11.20251117.tar.gz", hash = "sha256:1a6a3ac5394aa12bf547fae5d5eff91dceec46b6d07c4367d9b39a37f42f201a", size = 18100, upload-time = "2025-11-17T03:08:00.78Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/65/db/4d96bd8a069d3c3c3105e4a961a356cbac6aef162d25defb842bf8e50874/types_html5lib-1.1.11.20251115-py3-none-any.whl", hash = "sha256:6cee9173cdf4a3494174b84c8c78adffabb8a1d4f532da4edaad3ec1643fc6e7", size = 24074, upload-time = "2025-11-15T03:00:16.242Z" },
{ url = "https://files.pythonhosted.org/packages/f0/ab/f5606db367c1f57f7400d3cb3bead6665ee2509621439af1b29c35ef6f9e/types_html5lib-1.1.11.20251117-py3-none-any.whl", hash = "sha256:2a3fc935de788a4d2659f4535002a421e05bea5e172b649d33232e99d4272d08", size = 24302, upload-time = "2025-11-17T03:07:59.996Z" },
]
[[package]]
name = "types-lxml"
version = "2025.8.25"
version = "2026.2.16"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
{ name = "cssselect" },
{ name = "types-html5lib" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e0/3e/a545ece610c1bd9699addd887edfe9477a8f647c4336ba75cfb0561d197c/types_lxml-2025.8.25.tar.gz", hash = "sha256:79b9f5b1f236f937f14fe3add9dc687bd8d4111ca5df58eb9f1bde1a3b032fd5", size = 156126, upload-time = "2025-08-26T06:28:56.793Z" }
sdist = { url = "https://files.pythonhosted.org/packages/dd/ad/c70ac8cbdc28eb58a17301c69b4925af54b614e47f9b2ebc9de5cc10f786/types_lxml-2026.2.16.tar.gz", hash = "sha256:b3a1340cc06db98d541c785732f6f68bea438daff4e2b7809ef748d545d01406", size = 161204, upload-time = "2026-02-17T02:34:50.855Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/29/c45f567b4142288b8184f073af8f659abd134c21de055f971c65f2d755bd/types_lxml-2025.8.25-py3-none-any.whl", hash = "sha256:d61340e5329e102d3f8d64124e90d50c12c0bfeaa9088d65558279ef4e7138ac", size = 95318, upload-time = "2025-08-26T06:28:54.066Z" },
{ url = "https://files.pythonhosted.org/packages/5f/5c/03ec9befbf4bb5309bfd576c6a5ac1c75633f78f6b64cf1f594e97cd3d23/types_lxml-2026.2.16-py3-none-any.whl", hash = "sha256:5dd81ffa54830e5f361988737c5f1d6a0ae48b2742790637ec560df790ea0401", size = 97040, upload-time = "2026-02-17T02:34:49.286Z" },
]
[[package]]
@@ -677,9 +740,9 @@ wheels = [
[[package]]
name = "urllib3"
version = "2.5.0"
version = "2.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
]