diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 26be946..219b2ad 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -9,8 +9,15 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - name: Build and publish to pypi
- uses: JRubics/poetry-publish@v1.13
- with:
- pypi_token: ${{ secrets.PYPI_TOKEN }}
+ - uses: actions/checkout@v6
+
+ - name: Setup uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ version: "latest"
+
+ - name: Build distribution
+ run: uv build
+
+ - name: Publish to PyPI
+ run: uv publish
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 8cf05d0..24c6f0d 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -6,9 +6,10 @@ name: PyTest
on:
# Triggers the workflow on push or pull request events but only for the "master" branch
push:
- branches: [ "master" ]
+ branches: [ "**" ]
pull_request:
- branches: [ "master" ]
+ branches: [ "**" ]
+ types: [ opened, synchronize, reopened ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -17,13 +18,19 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - run: pipx install poetry
- - uses: actions/setup-python@v4
- with:
- python-version: '3.8'
- cache: 'poetry'
- - run: poetry install --with test
- - run: poetry run pytest
- env:
- QT_QPA_PLATFORM: "offscreen"
+ - uses: actions/checkout@v6
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+
+ - name: Set up Python
+ run: uv python install
+
+ - name: Install the project
+ run: uv sync --locked --all-extras --dev
+
+ - run: uv run pytest
+ env:
+ QT_QPA_PLATFORM: "offscreen"
diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml
new file mode 100644
index 0000000..5f01353
--- /dev/null
+++ b/.github/workflows/ruff.yaml
@@ -0,0 +1,31 @@
+name: ruff
+
+on:
+ push:
+ branches: [ '**' ]
+ pull_request:
+ branches: [ '**' ]
+ types: [ opened, synchronize, reopened ]
+
+jobs:
+ type-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+
+ - name: Set up Python
+ run: uv python install
+
+ - name: Install the project
+ run: uv sync --locked --all-extras --dev
+
+ - name: Run Ruff
+ run: uvx ruff check --output-format=github .
+
+ - name: Run Ty
+ run: uvx ty check
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 621ca9d..e897222 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -1,22 +1,18 @@
-# Read the Docs configuration file
-# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
-
-# Required
version: 2
-build:
- os: ubuntu-22.04
- tools:
- python: "3.12"
-
-# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
-# Optionally build your docs in additional formats such as PDF and ePub
-formats: all
-
-# Optionally set the version of Python and requirements required to build your docs
-python:
- install:
- - requirements: docs/requirements.readthedocs.txt
+build:
+ os: ubuntu-24.04
+ tools:
+ python: "3.13"
+ jobs:
+ pre_create_environment:
+ - asdf plugin add uv
+ - asdf install uv latest
+ - asdf global uv latest
+ create_environment:
+ - uv venv "${READTHEDOCS_VIRTUALENV_PATH}"
+ install:
+ - UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv sync --frozen --group docs
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..9c17a3e
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,103 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Pylustrator is an interactive Python tool for reproducible figure creation in scientific publications. It provides a GUI integrated with Matplotlib that allows users to interactively drag/resize plot elements, adjust formatting, and automatically generate Python code that reproduces all modifications.
+
+## Build & Development Commands
+
+```bash
+# Install dependencies
+uv sync
+
+# Install with dev dependencies (includes ruff linter)
+uv sync --extra dev
+
+# Install with test dependencies
+uv sync --extra test
+
+# Run tests
+uv run pytest
+
+# Run tests in headless mode (required for CI/Linux)
+QT_QPA_PLATFORM=offscreen uv run pytest
+
+# Run a single test
+uv run pytest tests/test_axes.py::TestAxes::test_method_name
+
+# Lint with ruff
+uv run ruff check .
+
+# Build documentation
+uv sync --group docs
+uv run sphinx-build -b html docs docs/_build
+```
+
+## Architecture
+
+### Core Flow
+```
+pylustrator.start() → patches plt.figure/plt.show
+ ↓
+PlotWindow (Qt GUI) created for each figure
+ ↓
+DragManager handles interactions → Selection manages multi-element transforms
+ ↓
+ChangeTracker monitors modifications → generates Python code
+ ↓
+Code inserted into source file before plt.show()
+```
+
+### Key Modules
+
+| Module | Purpose |
+|--------|---------|
+| `QtGuiDrag.py` | Entry point; patches matplotlib, creates PlotWindow |
+| `change_tracker.py` | Tracks modifications, generates reproducible Python code, manages undo/redo |
+| `drag_helper.py` | Mouse interactions, object selection, grabber classes |
+| `snap.py` | Alignment snapping; `TargetWrapper` provides unified interface for matplotlib objects |
+| `QLinkableWidgets.py` | `Linkable` mixin binds Qt widgets to matplotlib artist properties bidirectionally |
+| `components/plot_layout.py` | Graphics scene/view management, DPI awareness |
+| `components/qitem_properties.py` | Property editor panel (largest component) |
+| `helper_functions.py` | Utility functions like `fig_text()`, `add_axes()`, `changeFigureSize()` |
+
+### Key Patterns
+
+1. **Matplotlib Monkey-Patching**: `initialize()` patches `plt.figure()` and `plt.show()` to create GUI windows
+
+2. **TargetWrapper**: Unified interface for all matplotlib artists in `snap.py`:
+ ```python
+ wrapper = TargetWrapper(matplotlib_artist)
+ position = wrapper.get_position()
+ wrapper.set_position([x, y, w, h])
+ ```
+
+3. **Linkable Mixin**: Qt widgets auto-sync with matplotlib properties in `QLinkableWidgets.py`
+
+4. **Stack Introspection**: `ChangeTracker` uses traceback to find where to insert generated code in source files
+
+5. **Special Attributes**: Objects use `_pylustrator_*` attributes for tracking:
+ - `_pylustrator_reference` - object creation location
+ - `_pylustrator_old_args` - initial property values
+ - `_pylustrator_cached_*` - property caching
+
+### Testing
+
+Tests use `BaseTest` class from `tests/base_test_class.py` with helpers:
+- `run_plot_script()` - creates and runs test figures
+- `move_element()` - simulates drag operations
+- `check_line_in_file()` - verifies generated code
+- `change_property2()` - tests property modifications
+
+## Dependencies & Compatibility
+
+- Python 3.9+
+- Matplotlib 2.0+ (branching for 3.6.0+ features)
+- PyQt5 5.6+ (with PyQt6/PySide6 support)
+- macOS Retina/DPI awareness is actively maintained
+
+## Current Development Focus
+
+The `fix_mac_display` branch addresses macOS display scaling with DPR (Device Pixel Ratio) awareness in `plot_layout.py`, `drag_helper.py`, and `snap.py`.
diff --git a/development/add_copyright_notice.py b/development/add_copyright_notice.py
index 95fff49..3b06576 100644
--- a/development/add_copyright_notice.py
+++ b/development/add_copyright_notice.py
@@ -12,15 +12,17 @@
if file.endswith(".py"):
print(file)
full_filename = os.path.join(root, file)
- with open(full_filename+".tmp", "w") as fp2:
+ with open(full_filename + ".tmp", "w") as fp2:
fp2.write(notice.format(file))
with open(full_filename, "r") as fp1:
start = False
for line in fp1.readlines():
- if not start and not (line.startswith("#") or line.strip() == ""):
+ if not start and not (
+ line.startswith("#") or line.strip() == ""
+ ):
start = True
if start:
fp2.write(line)
continue
- shutil.move(full_filename+".tmp", full_filename)
+ shutil.move(full_filename + ".tmp", full_filename)
diff --git a/development/raise_version_number.py b/development/raise_version_number.py
index 9d68fa3..144c440 100644
--- a/development/raise_version_number.py
+++ b/development/raise_version_number.py
@@ -33,23 +33,29 @@
try:
new_version = sys.argv[1]
except IndexError:
- print(f"ERROR: no new version number supplied. Current {package_name} version is {current_version}", file=sys.stderr)
+ print(
+ f"ERROR: no new version number supplied. Current {package_name} version is {current_version}",
+ file=sys.stderr,
+ )
sys.exit(1)
# check if new version name differs
if current_version == new_version:
- print(f"ERROR: new {package_name} version {new_version} is the same as old version {current_version}.", file=sys.stderr)
+ print(
+ f"ERROR: new {package_name} version {new_version} is the same as old version {current_version}.",
+ file=sys.stderr,
+ )
sys.exit(1)
print(f"setting {package_name} version number from {current_version} to {new_version}")
-files = ["setup.py", "meta.yaml", "docs/conf.py", package_name+"/__init__.py"]
+files = ["pyproject.toml", "meta.yaml", "docs/conf.py", package_name + "/__init__.py"]
# Let's go
for file in files:
if replace_version(file, current_version, new_version):
os.system(f"git add {file}")
-
+
# commit changes
-os.system("git commit -m \"set version to v%s\"" % new_version)
-os.system("git tag \"v%s\"" % new_version)
+os.system('git commit -m "set version to v%s"' % new_version)
+os.system('git tag "v%s"' % new_version)
diff --git a/development/release_tools.py b/development/release_tools.py
index 193d3a6..719238e 100644
--- a/development/release_tools.py
+++ b/development/release_tools.py
@@ -40,7 +40,7 @@ def replace_version(file, version_old, version_new):
def get_setup_properties():
path = Path(__file__).parent
for i in range(3):
- setup_file = path / "setup.py"
+ setup_file = path / "pyproject.toml"
if setup_file.exists():
os.chdir(setup_file.parent)
with setup_file.open("r") as fp:
diff --git a/development/send_to_pypi.py b/development/send_to_pypi.py
deleted file mode 100644
index a27437d..0000000
--- a/development/send_to_pypi.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# send_to_pypi.py
-
-# Copyright (c) 2016-2020, Richard Gerum
-#
-# This file is part of Pylustrator.
-#
-# Pylustrator is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pylustrator is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pylustrator. If not, see
-
-from __future__ import print_function, division
-import os
-import sys
-
-current_folder = os.getcwd()
-
-try:
- # go to parent folder
- os.chdir(os.path.join(os.path.dirname(__file__), ".."))
-
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "pylustrator"))
- import pylustrator
-
- current_version = pylustrator.__version__
-
- from optparse import OptionParser
-
- parser = OptionParser()
- parser.add_option("-u", "--username", action="store", dest="username")
- parser.add_option("-p", "--password", action="store", dest="password")
- (options, args) = parser.parse_args()
-
- # upgrade twine (used for uploading)
- os.system("pip install twine --upgrade")
-
- # pack clickpoints
- os.system("python setup.py sdist")
-
- # the command
- command_string = "twine upload dist/pylustrator-%s.tar.gz" % current_version
- # optionally add the username
- if options.username:
- command_string += " --username %s" % options.username
- # optionally add the password
- if options.password:
- command_string += " --password %s" % options.password
- # print the command string
- print(command_string)
- # and execute it
- os.system(command_string)
-finally:
- os.chdir(current_folder)
diff --git a/docs/conf.py b/docs/conf.py
index 3b7ed8a..05c4208 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -21,7 +21,6 @@
import sys
import os
-import shlex
print(os.getcwd())
@@ -30,12 +29,13 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
-import mock
+import mock # noqa: E402
+
# try to import the modules of the package and mock everything that is not found
while True:
try:
# here are the modules that should be imported for the documentation
- import pylustrator
+ import pylustrator # noqa: F401
# if an import error occurs
except ImportError as err:
# get the module name from the error message
@@ -51,46 +51,46 @@
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.napoleon',
- 'sphinx.ext.mathjax',
- 'sphinx.ext.viewcode',
-# 'nbsphinx',
+ "sphinx.ext.autodoc",
+ "sphinx.ext.napoleon",
+ "sphinx.ext.mathjax",
+ "sphinx.ext.viewcode",
+ # 'nbsphinx',
]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'pylustrator'
-copyright = u'2018, Richard Gerum'
-author = u'Richard Gerum'
+project = "pylustrator"
+copyright = "2018, Richard Gerum"
+author = "Richard Gerum"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '1.3.0'
+version = "1.3.0"
# The full version, including alpha/beta/rc tags.
-release = '1.3.0'
+release = "1.3.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -101,37 +101,37 @@
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
-#today = ''
+# today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
+exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
-#default_role = None
+# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
-#show_authors = False
+# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
@@ -141,161 +141,160 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-import sphinx_rtd_theme
+import sphinx_rtd_theme # noqa: E402
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-#html_theme = 'alabaster'
+# html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
-#html_title = None
+# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-#html_favicon = None
+# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
+# html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
# If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
# If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
# If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
-#html_search_language = 'en'
+# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
-#html_search_options = {'type': 'default'}
+# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
-#html_search_scorer = 'scorer.js'
+# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
-htmlhelp_basename = 'pylustratordoc'
+htmlhelp_basename = "pylustratordoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-
-# Latex figure (float) alignment
-#'figure_align': 'htbp',
+ # The paper size ('letterpaper' or 'a4paper').
+ #'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #'preamble': '',
+ # Latex figure (float) alignment
+ #'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- (master_doc, 'pylustrator.tex', u'pylustrator Documentation',
- u'Richard Gerum', 'manual'),
+ (
+ master_doc,
+ "pylustrator.tex",
+ "pylustrator Documentation",
+ "Richard Gerum",
+ "manual",
+ ),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
-#latex_logo = None
+# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
# If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
# If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
# Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
# If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
-man_pages = [
- (master_doc, 'pylustrator', u'Pylustrator Documentation',
- [author], 1)
-]
+man_pages = [(master_doc, "pylustrator", "Pylustrator Documentation", [author], 1)]
# If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -304,19 +303,25 @@
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- (master_doc, 'pylustrator', u'Pylustrator Documentation',
- author, 'pylustrator', 'One line description of project.',
- 'Miscellaneous'),
+ (
+ master_doc,
+ "pylustrator",
+ "Pylustrator Documentation",
+ author,
+ "pylustrator",
+ "One line description of project.",
+ "Miscellaneous",
+ ),
]
# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
# If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
diff --git a/docs/example_pylustrator.py b/docs/example_pylustrator.py
index 7712ce2..06b3c02 100644
--- a/docs/example_pylustrator.py
+++ b/docs/example_pylustrator.py
@@ -12,7 +12,7 @@
np.random.seed(1)
t = np.arange(0.0, 2, 0.001)
y = 2 * np.sin(np.pi * t)
-a, b = np.random.normal(loc=(5., 3.), scale=(2., 4.), size=(100,2)).T
+a, b = np.random.normal(loc=(5.0, 3.0), scale=(2.0, 4.0), size=(100, 2)).T
b += a
plt.figure(1)
@@ -27,4 +27,10 @@
plt.bar(1, np.mean(b))
# show the plot in a pylustrator window
+#% start: automatic generated code from pylustrator
+plt.figure(1).ax_dict = {ax.get_label(): ax for ax in plt.figure(1).axes}
+import matplotlib as mpl
+getattr(plt.figure(1), '_pylustrator_init', lambda: ...)()
+plt.figure(1).axes[2].patches[0].set_facecolor("#008f00ff")
+#% end: automatic generated code from pylustrator
plt.show()
diff --git a/docs/plot2.py b/docs/plot2.py
index fb3761c..9f3fd36 100644
--- a/docs/plot2.py
+++ b/docs/plot2.py
@@ -2,9 +2,7 @@
import numpy as np
np.random.seed(1)
-a, b = np.random.normal(loc=(5., 3.),
- scale=(2., 4.),
- size=(100, 2)).T
+a, b = np.random.normal(loc=(5.0, 3.0), scale=(2.0, 4.0), size=(100, 2)).T
b += a
plt.figure(0, (4, 4))
diff --git a/matplotlib-stubs/figure.pyi b/matplotlib-stubs/figure.pyi
new file mode 100644
index 0000000..ce8b786
--- /dev/null
+++ b/matplotlib-stubs/figure.pyi
@@ -0,0 +1,65 @@
+from matplotlib.figure import _AxesStack # ty:ignore[unresolved-import]
+from matplotlib.axes import Axes
+from matplotlib.legend import Legend
+from matplotlib.patches import Patch
+
+from pylustrator.QtGuiDrag import PlotWindow
+from pylustrator.change_tracker import ChangeTracker
+from pylustrator.drag_helper import DragManager, GrabbableRectangleSelection
+from typing import Any, List, Callable, Tuple
+from matplotlib.text import Text
+from matplotlib.transforms import Transform
+
+
+class Figure:
+ window: PlotWindow
+ change_tracker: ChangeTracker
+ figure_dragger: DragManager
+ selection: GrabbableRectangleSelection
+ transFigure: Transform
+ get_size_inches: Callable[[], Tuple[float, float]]
+
+ def set_size_inches(
+ self, w: float, h: float, *args: Any, **kwargs: Any
+ ) -> None: ...
+
+ dpi: float
+ canvas: Any
+ figure: Any
+ bbox: Any
+ axes: List[Axes]
+ texts: List[Text]
+ patches: List[Patch]
+ legends: List[Legend]
+ subfigs: List[SubFigure]
+ number: int | float | str
+
+ def text(self, x: float, y: float, *args: Any, **kwargs: Any) -> Text: ...
+
+ def savefig(self, fname: Any, *args: Any, **kwargs: Any) -> None: ...
+
+ signals: Any
+ _pyl_graphics_scene_snapparent: Any
+ _axstack: _AxesStack
+ dpi_scale_trans: Any
+
+ def _make_key(self, ax: Axes) -> Any: ...
+
+ no_figure_dragger_selection_update: bool
+
+
+class SubFigure:
+ bbox: Any
+
+ axes: List[Axes]
+ texts: List[Text]
+ patches: List[Patch]
+ legends: List[Legend]
+ subfigs: List[SubFigure]
+
+ transFigure: Transform
+ transSubfigure: Transform
+
+ dpi_scale_trans: Any
+
+ _parent: Figure
diff --git a/matplotlib-stubs/matplotlib/__init__.pyi b/matplotlib-stubs/matplotlib/__init__.pyi
new file mode 100644
index 0000000..e69de29
diff --git a/matplotlib-stubs/matplotlib/text.pyi b/matplotlib-stubs/matplotlib/text.pyi
new file mode 100644
index 0000000..59a04c9
--- /dev/null
+++ b/matplotlib-stubs/matplotlib/text.pyi
@@ -0,0 +1,49 @@
+from typing import Any, Tuple
+
+
+class Text:
+ def get_fontname(self) -> str: ...
+
+ def set_fontname(self, name: str) -> None: ...
+
+ def get_fontweight(self) -> str: ...
+
+ def set_fontweight(self, weight: str) -> None: ...
+
+ def get_weight(self) -> str: ...
+
+ def set_weight(self, weight: str) -> None: ...
+
+ def get_fontstyle(self) -> str: ...
+
+ def set_fontstyle(self, style: str) -> None: ...
+
+ def get_style(self) -> str: ...
+
+ def set_style(self, style: str) -> None: ...
+
+ def get_fontsize(self) -> float: ...
+
+ def set_fontsize(self, size: float | int) -> None: ...
+
+ def get_horizontalalignment(self) -> str: ...
+
+ def set_horizontalalignment(self, align: str) -> None: ...
+
+ def get_ha(self) -> str: ...
+
+ def set_ha(self, align: str) -> None: ...
+
+ def get_va(self) -> str: ...
+
+ def set_color(self, color: Any) -> None: ...
+
+ def get_color(self) -> Any: ...
+
+ def get_text(self) -> str: ...
+
+ def get_rotation(self) -> float | str: ...
+
+ def get_position(self) -> Tuple[float, float]: ...
+
+ pad_offset: float
diff --git a/poetry.lock b/poetry.lock
deleted file mode 100644
index 62ab811..0000000
--- a/poetry.lock
+++ /dev/null
@@ -1,975 +0,0 @@
-# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
-
-[[package]]
-name = "colorama"
-version = "0.4.6"
-description = "Cross-platform colored terminal text."
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
-files = [
- {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
- {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
-]
-
-[[package]]
-name = "contourpy"
-version = "1.3.0"
-description = "Python library for calculating contours of 2D quadrilateral grids"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"},
- {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"},
- {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"},
- {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"},
- {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"},
- {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"},
- {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"},
- {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"},
- {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"},
- {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"},
- {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"},
- {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"},
- {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"},
- {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"},
- {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"},
- {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"},
- {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"},
- {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"},
- {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"},
- {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"},
- {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"},
- {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"},
- {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"},
- {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"},
- {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"},
- {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"},
- {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"},
- {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"},
- {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"},
- {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"},
- {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"},
- {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"},
- {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"},
- {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"},
- {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"},
- {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"},
- {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"},
- {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"},
- {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"},
- {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"},
- {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"},
-]
-
-[package.dependencies]
-numpy = ">=1.23"
-
-[package.extras]
-bokeh = ["bokeh", "selenium"]
-docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"]
-mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"]
-test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
-test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"]
-
-[[package]]
-name = "cycler"
-version = "0.12.1"
-description = "Composable style cycles"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
- {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
-]
-
-[package.extras]
-docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
-tests = ["pytest", "pytest-cov", "pytest-xdist"]
-
-[[package]]
-name = "exceptiongroup"
-version = "1.2.2"
-description = "Backport of PEP 654 (exception groups)"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
- {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
-]
-
-[package.extras]
-test = ["pytest (>=6)"]
-
-[[package]]
-name = "fonttools"
-version = "4.55.0"
-description = "Tools to manipulate font files"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "fonttools-4.55.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:51c029d4c0608a21a3d3d169dfc3fb776fde38f00b35ca11fdab63ba10a16f61"},
- {file = "fonttools-4.55.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bca35b4e411362feab28e576ea10f11268b1aeed883b9f22ed05675b1e06ac69"},
- {file = "fonttools-4.55.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ce4ba6981e10f7e0ccff6348e9775ce25ffadbee70c9fd1a3737e3e9f5fa74f"},
- {file = "fonttools-4.55.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31d00f9852a6051dac23294a4cf2df80ced85d1d173a61ba90a3d8f5abc63c60"},
- {file = "fonttools-4.55.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e198e494ca6e11f254bac37a680473a311a88cd40e58f9cc4dc4911dfb686ec6"},
- {file = "fonttools-4.55.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7208856f61770895e79732e1dcbe49d77bd5783adf73ae35f87fcc267df9db81"},
- {file = "fonttools-4.55.0-cp310-cp310-win32.whl", hash = "sha256:e7e6a352ff9e46e8ef8a3b1fe2c4478f8a553e1b5a479f2e899f9dc5f2055880"},
- {file = "fonttools-4.55.0-cp310-cp310-win_amd64.whl", hash = "sha256:636caaeefe586d7c84b5ee0734c1a5ab2dae619dc21c5cf336f304ddb8f6001b"},
- {file = "fonttools-4.55.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fa34aa175c91477485c44ddfbb51827d470011e558dfd5c7309eb31bef19ec51"},
- {file = "fonttools-4.55.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:37dbb3fdc2ef7302d3199fb12468481cbebaee849e4b04bc55b77c24e3c49189"},
- {file = "fonttools-4.55.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5263d8e7ef3c0ae87fbce7f3ec2f546dc898d44a337e95695af2cd5ea21a967"},
- {file = "fonttools-4.55.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f307f6b5bf9e86891213b293e538d292cd1677e06d9faaa4bf9c086ad5f132f6"},
- {file = "fonttools-4.55.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f0a4b52238e7b54f998d6a56b46a2c56b59c74d4f8a6747fb9d4042190f37cd3"},
- {file = "fonttools-4.55.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3e569711464f777a5d4ef522e781dc33f8095ab5efd7548958b36079a9f2f88c"},
- {file = "fonttools-4.55.0-cp311-cp311-win32.whl", hash = "sha256:2b3ab90ec0f7b76c983950ac601b58949f47aca14c3f21eed858b38d7ec42b05"},
- {file = "fonttools-4.55.0-cp311-cp311-win_amd64.whl", hash = "sha256:aa046f6a63bb2ad521004b2769095d4c9480c02c1efa7d7796b37826508980b6"},
- {file = "fonttools-4.55.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:838d2d8870f84fc785528a692e724f2379d5abd3fc9dad4d32f91cf99b41e4a7"},
- {file = "fonttools-4.55.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f46b863d74bab7bb0d395f3b68d3f52a03444964e67ce5c43ce43a75efce9246"},
- {file = "fonttools-4.55.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33b52a9cfe4e658e21b1f669f7309b4067910321757fec53802ca8f6eae96a5a"},
- {file = "fonttools-4.55.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:732a9a63d6ea4a81b1b25a1f2e5e143761b40c2e1b79bb2b68e4893f45139a40"},
- {file = "fonttools-4.55.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7dd91ac3fcb4c491bb4763b820bcab6c41c784111c24172616f02f4bc227c17d"},
- {file = "fonttools-4.55.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1f0e115281a32ff532118aa851ef497a1b7cda617f4621c1cdf81ace3e36fb0c"},
- {file = "fonttools-4.55.0-cp312-cp312-win32.whl", hash = "sha256:6c99b5205844f48a05cb58d4a8110a44d3038c67ed1d79eb733c4953c628b0f6"},
- {file = "fonttools-4.55.0-cp312-cp312-win_amd64.whl", hash = "sha256:f8c8c76037d05652510ae45be1cd8fb5dd2fd9afec92a25374ac82255993d57c"},
- {file = "fonttools-4.55.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8118dc571921dc9e4b288d9cb423ceaf886d195a2e5329cc427df82bba872cd9"},
- {file = "fonttools-4.55.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01124f2ca6c29fad4132d930da69158d3f49b2350e4a779e1efbe0e82bd63f6c"},
- {file = "fonttools-4.55.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ffd58d2691f11f7c8438796e9f21c374828805d33e83ff4b76e4635633674c"},
- {file = "fonttools-4.55.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5435e5f1eb893c35c2bc2b9cd3c9596b0fcb0a59e7a14121562986dd4c47b8dd"},
- {file = "fonttools-4.55.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d12081729280c39d001edd0f4f06d696014c26e6e9a0a55488fabc37c28945e4"},
- {file = "fonttools-4.55.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7ad1f1b98ab6cb927ab924a38a8649f1ffd7525c75fe5b594f5dab17af70e18"},
- {file = "fonttools-4.55.0-cp313-cp313-win32.whl", hash = "sha256:abe62987c37630dca69a104266277216de1023cf570c1643bb3a19a9509e7a1b"},
- {file = "fonttools-4.55.0-cp313-cp313-win_amd64.whl", hash = "sha256:2863555ba90b573e4201feaf87a7e71ca3b97c05aa4d63548a4b69ea16c9e998"},
- {file = "fonttools-4.55.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00f7cf55ad58a57ba421b6a40945b85ac7cc73094fb4949c41171d3619a3a47e"},
- {file = "fonttools-4.55.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f27526042efd6f67bfb0cc2f1610fa20364396f8b1fc5edb9f45bb815fb090b2"},
- {file = "fonttools-4.55.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e67974326af6a8879dc2a4ec63ab2910a1c1a9680ccd63e4a690950fceddbe"},
- {file = "fonttools-4.55.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61dc0a13451143c5e987dec5254d9d428f3c2789a549a7cf4f815b63b310c1cc"},
- {file = "fonttools-4.55.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b2e526b325a903868c62155a6a7e24df53f6ce4c5c3160214d8fe1be2c41b478"},
- {file = "fonttools-4.55.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b7ef9068a1297714e6fefe5932c33b058aa1d45a2b8be32a4c6dee602ae22b5c"},
- {file = "fonttools-4.55.0-cp38-cp38-win32.whl", hash = "sha256:55718e8071be35dff098976bc249fc243b58efa263768c611be17fe55975d40a"},
- {file = "fonttools-4.55.0-cp38-cp38-win_amd64.whl", hash = "sha256:553bd4f8cc327f310c20158e345e8174c8eed49937fb047a8bda51daf2c353c8"},
- {file = "fonttools-4.55.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f901cef813f7c318b77d1c5c14cf7403bae5cb977cede023e22ba4316f0a8f6"},
- {file = "fonttools-4.55.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c9679fc0dd7e8a5351d321d8d29a498255e69387590a86b596a45659a39eb0d"},
- {file = "fonttools-4.55.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2820a8b632f3307ebb0bf57948511c2208e34a4939cf978333bc0a3f11f838"},
- {file = "fonttools-4.55.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23bbbb49bec613a32ed1b43df0f2b172313cee690c2509f1af8fdedcf0a17438"},
- {file = "fonttools-4.55.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a656652e1f5d55b9728937a7e7d509b73d23109cddd4e89ee4f49bde03b736c6"},
- {file = "fonttools-4.55.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f50a1f455902208486fbca47ce33054208a4e437b38da49d6721ce2fef732fcf"},
- {file = "fonttools-4.55.0-cp39-cp39-win32.whl", hash = "sha256:161d1ac54c73d82a3cded44202d0218ab007fde8cf194a23d3dd83f7177a2f03"},
- {file = "fonttools-4.55.0-cp39-cp39-win_amd64.whl", hash = "sha256:ca7fd6987c68414fece41c96836e945e1f320cda56fc96ffdc16e54a44ec57a2"},
- {file = "fonttools-4.55.0-py3-none-any.whl", hash = "sha256:12db5888cd4dd3fcc9f0ee60c6edd3c7e1fd44b7dd0f31381ea03df68f8a153f"},
- {file = "fonttools-4.55.0.tar.gz", hash = "sha256:7636acc6ab733572d5e7eec922b254ead611f1cdad17be3f0be7418e8bfaca71"},
-]
-
-[package.extras]
-all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
-graphite = ["lz4 (>=1.7.4.2)"]
-interpolatable = ["munkres", "pycairo", "scipy"]
-lxml = ["lxml (>=4.0)"]
-pathops = ["skia-pathops (>=0.5.0)"]
-plot = ["matplotlib"]
-repacker = ["uharfbuzz (>=0.23.0)"]
-symfont = ["sympy"]
-type1 = ["xattr"]
-ufo = ["fs (>=2.2.0,<3)"]
-unicode = ["unicodedata2 (>=15.1.0)"]
-woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
-
-[[package]]
-name = "imageio"
-version = "2.36.0"
-description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats."
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "imageio-2.36.0-py3-none-any.whl", hash = "sha256:471f1eda55618ee44a3c9960911c35e647d9284c68f077e868df633398f137f0"},
- {file = "imageio-2.36.0.tar.gz", hash = "sha256:1c8f294db862c256e9562354d65aa54725b8dafed7f10f02bb3ec20ec1678850"},
-]
-
-[package.dependencies]
-numpy = "*"
-pillow = ">=8.3.2"
-
-[package.extras]
-all-plugins = ["astropy", "av", "imageio-ffmpeg", "numpy (>2)", "pillow-heif", "psutil", "rawpy", "tifffile"]
-all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"]
-build = ["wheel"]
-dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"]
-docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"]
-ffmpeg = ["imageio-ffmpeg", "psutil"]
-fits = ["astropy"]
-full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpy (>2)", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "rawpy", "sphinx (<6)", "tifffile", "wheel"]
-gdal = ["gdal"]
-itk = ["itk"]
-linting = ["black", "flake8"]
-pillow-heif = ["pillow-heif"]
-pyav = ["av"]
-rawpy = ["numpy (>2)", "rawpy"]
-test = ["fsspec[github]", "pytest", "pytest-cov"]
-tifffile = ["tifffile"]
-
-[[package]]
-name = "importlib-resources"
-version = "6.4.5"
-description = "Read resources from Python packages"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"},
- {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"},
-]
-
-[package.dependencies]
-zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
-cover = ["pytest-cov"]
-doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-enabler = ["pytest-enabler (>=2.2)"]
-test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"]
-type = ["pytest-mypy"]
-
-[[package]]
-name = "iniconfig"
-version = "2.0.0"
-description = "brain-dead simple config-ini parsing"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
- {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
-]
-
-[[package]]
-name = "kiwisolver"
-version = "1.4.7"
-description = "A fast implementation of the Cassowary constraint solver"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"},
- {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"},
- {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"},
- {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"},
- {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"},
- {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"},
- {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"},
- {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"},
- {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"},
- {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"},
- {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"},
- {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"},
- {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"},
- {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"},
- {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"},
- {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"},
- {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"},
- {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"},
- {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"},
- {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"},
- {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"},
- {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"},
- {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"},
- {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"},
- {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"},
- {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"},
- {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"},
- {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"},
- {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"},
- {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"},
- {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"},
- {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"},
- {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"},
- {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"},
- {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"},
- {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"},
-]
-
-[[package]]
-name = "lazy-loader"
-version = "0.4"
-description = "Makes it easy to load subpackages and functions on demand."
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"},
- {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"},
-]
-
-[package.dependencies]
-packaging = "*"
-
-[package.extras]
-dev = ["changelist (==0.5)"]
-lint = ["pre-commit (==3.7.0)"]
-test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
-
-[[package]]
-name = "matplotlib"
-version = "3.9.2"
-description = "Python plotting package"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"},
- {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"},
- {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"},
- {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"},
- {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"},
- {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"},
- {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"},
- {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"},
- {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"},
- {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"},
- {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"},
- {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"},
- {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"},
- {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"},
- {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"},
- {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"},
- {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"},
- {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"},
- {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"},
- {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"},
- {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"},
- {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"},
- {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"},
- {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"},
- {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"},
- {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"},
- {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"},
- {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"},
- {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"},
- {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"},
- {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"},
- {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"},
- {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"},
- {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"},
- {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"},
- {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"},
- {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"},
- {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"},
- {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"},
- {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"},
-]
-
-[package.dependencies]
-contourpy = ">=1.0.1"
-cycler = ">=0.10"
-fonttools = ">=4.22.0"
-importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""}
-kiwisolver = ">=1.3.1"
-numpy = ">=1.23"
-packaging = ">=20.0"
-pillow = ">=8"
-pyparsing = ">=2.3.1"
-python-dateutil = ">=2.7"
-
-[package.extras]
-dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"]
-
-[[package]]
-name = "natsort"
-version = "8.4.0"
-description = "Simple yet flexible natural sorting in Python."
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"},
- {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"},
-]
-
-[package.extras]
-fast = ["fastnumbers (>=2.0.0)"]
-icu = ["PyICU (>=1.0.0)"]
-
-[[package]]
-name = "networkx"
-version = "3.2.1"
-description = "Python package for creating and manipulating graphs and networks"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"},
- {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"},
-]
-
-[package.extras]
-default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"]
-developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"]
-doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"]
-extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"]
-test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"]
-
-[[package]]
-name = "numpy"
-version = "2.0.2"
-description = "Fundamental package for array computing in Python"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"},
- {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"},
- {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"},
- {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"},
- {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"},
- {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"},
- {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"},
- {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"},
- {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"},
- {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"},
- {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"},
- {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"},
- {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"},
- {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"},
- {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"},
- {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"},
- {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"},
- {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"},
- {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"},
- {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"},
- {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"},
- {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"},
- {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"},
- {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"},
- {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"},
- {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"},
- {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"},
- {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"},
- {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"},
- {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"},
- {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"},
- {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"},
- {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"},
- {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"},
- {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"},
- {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"},
- {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"},
- {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"},
- {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"},
- {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"},
- {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"},
- {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"},
- {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"},
- {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"},
- {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"},
-]
-
-[[package]]
-name = "packaging"
-version = "24.2"
-description = "Core utilities for Python packages"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
- {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
-]
-
-[[package]]
-name = "pillow"
-version = "11.0.0"
-description = "Python Imaging Library (Fork)"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"},
- {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"},
- {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"},
- {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"},
- {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"},
- {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"},
- {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"},
- {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"},
- {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"},
- {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"},
- {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"},
- {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"},
- {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"},
- {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"},
- {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"},
- {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"},
- {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"},
- {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"},
- {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"},
- {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"},
- {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"},
- {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"},
- {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"},
- {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"},
- {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"},
- {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"},
- {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"},
- {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"},
- {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"},
- {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"},
- {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"},
- {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"},
- {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"},
- {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"},
- {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"},
- {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"},
- {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"},
- {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"},
- {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"},
- {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"},
- {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"},
- {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"},
- {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"},
- {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"},
- {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"},
- {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"},
- {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"},
- {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"},
- {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"},
- {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"},
- {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"},
- {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"},
- {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"},
- {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"},
- {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"},
- {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"},
- {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"},
- {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"},
- {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"},
- {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"},
- {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"},
- {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"},
- {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"},
- {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"},
- {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"},
- {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"},
- {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"},
- {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"},
- {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"},
- {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"},
- {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"},
- {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"},
- {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"},
- {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"},
- {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"},
-]
-
-[package.extras]
-docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
-fpx = ["olefile"]
-mic = ["olefile"]
-tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
-typing = ["typing-extensions"]
-xmp = ["defusedxml"]
-
-[[package]]
-name = "pluggy"
-version = "1.5.0"
-description = "plugin and hook calling mechanisms for python"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
- {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
-]
-
-[package.extras]
-dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
-
-[[package]]
-name = "pyparsing"
-version = "3.2.0"
-description = "pyparsing module - Classes and methods to define and execute parsing grammars"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"},
- {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"},
-]
-
-[package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
-
-[[package]]
-name = "pyqt5"
-version = "5.15.11"
-description = "Python bindings for the Qt cross platform application toolkit"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "PyQt5-5.15.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2"},
- {file = "PyQt5-5.15.11-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6"},
- {file = "PyQt5-5.15.11-cp38-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1"},
- {file = "PyQt5-5.15.11-cp38-abi3-win32.whl", hash = "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd"},
- {file = "PyQt5-5.15.11-cp38-abi3-win_amd64.whl", hash = "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517"},
- {file = "PyQt5-5.15.11.tar.gz", hash = "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52"},
-]
-
-[package.dependencies]
-PyQt5-Qt5 = ">=5.15.2,<5.16.0"
-PyQt5-sip = ">=12.15,<13"
-
-[[package]]
-name = "pyqt5-qt5"
-version = "5.15.15"
-description = "The subset of a Qt installation needed by PyQt5."
-optional = false
-python-versions = "*"
-files = [
- {file = "PyQt5_Qt5-5.15.15-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:eb74072935958a830887115b1de1ff26341fc2d5881b28129de39612b10a260e"},
- {file = "PyQt5_Qt5-5.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f8b174725fbe29c1a22f8acce5798933a65c8a083f1d9833ff212479ec2b3c14"},
- {file = "PyQt5_Qt5-5.15.15-py3-none-manylinux2014_x86_64.whl", hash = "sha256:611505d04ffb06a5e5bcf98f5ff0e4e15ba7785565ccbe7bd3b2e40642ea3bdd"},
-]
-
-[[package]]
-name = "pyqt5-sip"
-version = "12.15.0"
-description = "The sip module support for PyQt5"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "PyQt5_sip-12.15.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:749f7a3ffd6e3d2d5db65ed92c95cbd14490631595c61f0c0672c9238bfb17de"},
- {file = "PyQt5_sip-12.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b4adc529fa4ec05728e14ea55194d907cc51f18d6f2ac5cc9f6eb52ac038aa0f"},
- {file = "PyQt5_sip-12.15.0-cp310-cp310-win32.whl", hash = "sha256:83d247cdc43ef224410b14c97413067ea26356dfa39e9ed0fe702a31e25710b0"},
- {file = "PyQt5_sip-12.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:13f0c6a78e781255863e3e160304648efaf62276b7102741af637b63a6e96930"},
- {file = "PyQt5_sip-12.15.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:855563d4d3b59ce7438bbf2dd32fed2707787defa40f3efe94f204a19ef92b25"},
- {file = "PyQt5_sip-12.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b718a362f4392430903bbb2a4b9bbff9841a16a52f0cfdd5b5bbd9d11457980"},
- {file = "PyQt5_sip-12.15.0-cp311-cp311-win32.whl", hash = "sha256:2575f428de584a12009fd29d00c89df16ed101a3b38beba818dfdcbc4a10709c"},
- {file = "PyQt5_sip-12.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c85be433fbafcb3d417581c0e1b67c8198d23858166e4f938e971c2262c13cdb"},
- {file = "PyQt5_sip-12.15.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:852b75cf208825602480e95ab63314108f872d0da251e9ad3deaaff5a183a6f5"},
- {file = "PyQt5_sip-12.15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0cd21c3215e3c47fdd5fa7a2dc3dd1e07a7230b0626e905a7217925068c788b9"},
- {file = "PyQt5_sip-12.15.0-cp312-cp312-win32.whl", hash = "sha256:b58eeedc9b2a3037b136bf96915196c391a33be470ed1c0723d7163ef0b727a2"},
- {file = "PyQt5_sip-12.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:24a1d4937332bf0a38dd95bb2ce4d89723df449f6e912b52ef0e107e11fefac1"},
- {file = "PyQt5_sip-12.15.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:91b9538458a3a23e033c213bc879ce64f3d0a33d5a49cbd03e1e584efe307a35"},
- {file = "PyQt5_sip-12.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0c1c727ede7fdc464a1fe2e46109ba836509b2d7187a46fdeae443148ce51d1c"},
- {file = "PyQt5_sip-12.15.0-cp38-cp38-win32.whl", hash = "sha256:dd241de9c569c07bbba62bff1049996e5b52478164f61f430073a87bf6d26d33"},
- {file = "PyQt5_sip-12.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:f600ae6f03e4bff91153c0dc7ebe52f90bd2b6afda58fd580e6990b3b951adc0"},
- {file = "PyQt5_sip-12.15.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0c543d604116af26694a8a5ba90f510551ff9124d503ae5ee14bb73a61363a3"},
- {file = "PyQt5_sip-12.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:97f2d6e8d9b7b3d3e795d576d7f56e6257f524221f6383b33ded7287763e9f06"},
- {file = "PyQt5_sip-12.15.0-cp39-cp39-win32.whl", hash = "sha256:ed5221c6241981bd98d39504823efb9cbe36841bf8917288f8fe8fc1d5569a41"},
- {file = "PyQt5_sip-12.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:7f88c85702dce80ac2e1a162054f688ed394811d6dd03a5574b3fa8111b0a6db"},
- {file = "PyQt5_sip-12.15.0.tar.gz", hash = "sha256:d23fdfcf363b5cedd9d39f8a9c5710e7d52804f5b08a58e91c638b36eafcb702"},
-]
-
-[[package]]
-name = "pytest"
-version = "7.4.4"
-description = "pytest: simple powerful testing with Python"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
- {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
-]
-
-[package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
-pluggy = ">=0.12,<2.0"
-tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
-
-[package.extras]
-testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
-
-[[package]]
-name = "python-dateutil"
-version = "2.9.0.post0"
-description = "Extensions to the standard Python datetime module"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-files = [
- {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
- {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
-]
-
-[package.dependencies]
-six = ">=1.5"
-
-[[package]]
-name = "qtawesome"
-version = "1.3.1"
-description = "FontAwesome icons in PyQt and PySide applications"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "QtAwesome-1.3.1-py3-none-any.whl", hash = "sha256:6078449035cd5311bc461e99940a426c27cb919fb4211c8cfc64506cb71e2dd7"},
- {file = "QtAwesome-1.3.1.tar.gz", hash = "sha256:075b2c9ee01cbaf5e3a4bebed0e5529ee8605981355f21dea051b15c1b869e1b"},
-]
-
-[package.dependencies]
-qtpy = "*"
-
-[[package]]
-name = "qtpy"
-version = "2.4.2"
-description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)."
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "QtPy-2.4.2-py3-none-any.whl", hash = "sha256:5a696b1dd7a354cb330657da1d17c20c2190c72d4888ba923f8461da67aa1a1c"},
- {file = "qtpy-2.4.2.tar.gz", hash = "sha256:9d6ec91a587cc1495eaebd23130f7619afa5cdd34a277acb87735b4ad7c65156"},
-]
-
-[package.dependencies]
-packaging = "*"
-
-[package.extras]
-test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"]
-
-[[package]]
-name = "scikit-image"
-version = "0.24.0"
-description = "Image processing in Python"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb3bc0264b6ab30b43c4179ee6156bc18b4861e78bb329dd8d16537b7bbf827a"},
- {file = "scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9c7a52e20cdd760738da38564ba1fed7942b623c0317489af1a598a8dedf088b"},
- {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f46e6ce42e5409f4d09ce1b0c7f80dd7e4373bcec635b6348b63e3c886eac8"},
- {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39ee0af13435c57351a3397eb379e72164ff85161923eec0c38849fecf1b4764"},
- {file = "scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7"},
- {file = "scikit_image-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:272909e02a59cea3ed4aa03739bb88df2625daa809f633f40b5053cf09241831"},
- {file = "scikit_image-0.24.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:190ebde80b4470fe8838764b9b15f232a964f1a20391663e31008d76f0c696f7"},
- {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c98cc695005faf2b79904e4663796c977af22586ddf1b12d6af2fa22842dc2"},
- {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa27b3a0dbad807b966b8db2d78da734cb812ca4787f7fbb143764800ce2fa9c"},
- {file = "scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c"},
- {file = "scikit_image-0.24.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6fccceb54c9574590abcddc8caf6cefa57c13b5b8b4260ab3ff88ad8f3c252b3"},
- {file = "scikit_image-0.24.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ccc01e4760d655aab7601c1ba7aa4ddd8b46f494ac46ec9c268df6f33ccddf4c"},
- {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18836a18d3a7b6aca5376a2d805f0045826bc6c9fc85331659c33b4813e0b563"},
- {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8579bda9c3f78cb3b3ed8b9425213c53a25fa7e994b7ac01f2440b395babf660"},
- {file = "scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc"},
- {file = "scikit_image-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef04360eda372ee5cd60aebe9be91258639c86ae2ea24093fb9182118008d009"},
- {file = "scikit_image-0.24.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e9aadb442360a7e76f0c5c9d105f79a83d6df0e01e431bd1d5757e2c5871a1f3"},
- {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e37de6f4c1abcf794e13c258dc9b7d385d5be868441de11c180363824192ff7"},
- {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4688c18bd7ec33c08d7bf0fd19549be246d90d5f2c1d795a89986629af0a1e83"},
- {file = "scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69"},
- {file = "scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab"},
-]
-
-[package.dependencies]
-imageio = ">=2.33"
-lazy-loader = ">=0.4"
-networkx = ">=2.8"
-numpy = ">=1.23"
-packaging = ">=21"
-pillow = ">=9.1"
-scipy = ">=1.9"
-tifffile = ">=2022.8.12"
-
-[package.extras]
-build = ["Cython (>=3.0.4)", "build", "meson-python (>=0.15)", "ninja", "numpy (>=2.0.0rc1)", "packaging (>=21)", "pythran", "setuptools (>=67)", "spin (==0.8)", "wheel"]
-data = ["pooch (>=1.6.0)"]
-developer = ["ipython", "pre-commit", "tomli"]
-docs = ["PyWavelets (>=1.1.1)", "dask[array] (>=2022.9.2)", "ipykernel", "ipywidgets", "kaleido", "matplotlib (>=3.6)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.15.2)", "pytest-doctestplus", "pytest-runner", "scikit-learn (>=1.1)", "seaborn (>=0.11)", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-gallery (>=0.14)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"]
-optional = ["PyWavelets (>=1.1.1)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=0.2.1)", "dask[array] (>=2021.1.0)", "matplotlib (>=3.6)", "pooch (>=1.6.0)", "pyamg", "scikit-learn (>=1.1)"]
-test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=7.0)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"]
-
-[[package]]
-name = "scipy"
-version = "1.13.1"
-description = "Fundamental algorithms for scientific computing in Python"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"},
- {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"},
- {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"},
- {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"},
- {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"},
- {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"},
- {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"},
- {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"},
- {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"},
- {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"},
- {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"},
- {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"},
- {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"},
- {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"},
- {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"},
- {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"},
- {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"},
- {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"},
- {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"},
- {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"},
- {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"},
- {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"},
- {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"},
- {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"},
- {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"},
-]
-
-[package.dependencies]
-numpy = ">=1.22.4,<2.3"
-
-[package.extras]
-dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"]
-doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"]
-test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
-
-[[package]]
-name = "six"
-version = "1.16.0"
-description = "Python 2 and 3 compatibility utilities"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-files = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-
-[[package]]
-name = "tifffile"
-version = "2024.8.30"
-description = "Read and write TIFF files"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "tifffile-2024.8.30-py3-none-any.whl", hash = "sha256:8bc59a8f02a2665cd50a910ec64961c5373bee0b8850ec89d3b7b485bf7be7ad"},
- {file = "tifffile-2024.8.30.tar.gz", hash = "sha256:2c9508fe768962e30f87def61819183fb07692c258cb175b3c114828368485a4"},
-]
-
-[package.dependencies]
-numpy = "*"
-
-[package.extras]
-all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib", "zarr"]
-codecs = ["imagecodecs (>=2023.8.12)"]
-plot = ["matplotlib"]
-test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "roifile", "xarray", "zarr"]
-xml = ["defusedxml", "lxml"]
-zarr = ["fsspec", "zarr"]
-
-[[package]]
-name = "tomli"
-version = "2.1.0"
-description = "A lil' TOML parser"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
- {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
-]
-
-[[package]]
-name = "zipp"
-version = "3.21.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
- {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
-]
-
-[package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
-cover = ["pytest-cov"]
-doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-enabler = ["pytest-enabler (>=2.2)"]
-test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
-type = ["pytest-mypy"]
-
-[metadata]
-lock-version = "2.0"
-python-versions = ">=3.9"
-content-hash = "60414ee022fc5582e48c83a2d952b35a50000aed277af4b62a4c0cd77bf2ad0a"
diff --git a/pylustrator/QLinkableWidgets.py b/pylustrator/QLinkableWidgets.py
index 3b8ca21..ab91591 100644
--- a/pylustrator/QLinkableWidgets.py
+++ b/pylustrator/QLinkableWidgets.py
@@ -19,25 +19,39 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from typing import Optional, Sequence
+from typing import TYPE_CHECKING, Optional, Sequence, Callable
import matplotlib as mpl
import matplotlib.pyplot as plt
-import matplotlib.transforms as transforms
import numpy as np
from matplotlib.artist import Artist
-from matplotlib.figure import Figure
from matplotlib.text import Text
from matplotlib.axes import Axes
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from matplotlib.figure import Figure
+from matplotlib.transforms import Transform
+
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
+ from PyQt5.QtCore import pyqtSignal as Signal
+else:
+ from qtpy import QtCore, QtGui, QtWidgets
+ from qtpy.QtCore import Signal
+from PyQt5.QtCore import pyqtBoundSignal
from .helper_functions import main_figure
class Linkable:
- """ a class that automatically links a widget with the property of a matplotlib artist
- """
+ """a class that automatically links a widget with the property of a matplotlib artist"""
+
+ editingFinished = Signal()
- def link(self, property_name: str, signal: QtCore.Signal = None, condition: callable = None, direct: bool = False):
+ def link(
+ self,
+ property_name: str,
+ signal: pyqtBoundSignal | None = None,
+ condition: Optional[Callable] = None,
+ direct: bool = False,
+ ):
self.element = None
self.direct = direct
self.property_name = property_name
@@ -63,21 +77,34 @@ def set(v):
self.getLinkedProperty = get
self.serializeLinkedProperty = lambda x: "." + property_name + " = %s" % x
else:
+
def set(v, v_list=None):
if v_list is None:
- v = [v]+[v]*len(main_figure(self.element).selection.targets)
+ v = [v] + [v] * len(main_figure(self.element).selection.targets)
else:
v = v_list
# special treatment for the xylabels, as they are not directly the target objects
label_object = None
- if isinstance(self.element, Text) and len(main_figure(self.element).selection.targets) and isinstance(main_figure(self.element).selection.targets[0].target, Axes):
+ if (
+ isinstance(self.element, Text)
+ and len(main_figure(self.element).selection.targets)
+ and isinstance(
+ main_figure(self.element).selection.targets[0].target, Axes
+ )
+ ):
for elm in main_figure(self.element).selection.targets:
elm = elm.target
- if self.element == getattr(getattr(elm, f"get_xaxis")(), "get_label")():
+ if (
+ self.element
+ == getattr(getattr(elm, "get_xaxis")(), "get_label")()
+ ):
label_object = "x"
break
- if self.element == getattr(getattr(elm, f"get_yaxis")(), "get_label")():
+ if (
+ self.element
+ == getattr(getattr(elm, "get_yaxis")(), "get_label")()
+ ):
label_object = "y"
break
@@ -89,12 +116,16 @@ def set(v, v_list=None):
elm = elm.target
# special treatment for the xylabels, as they are not directly the target objects
if label_object is not None:
- elm = getattr(getattr(elm, f"get_{label_object}axis")(), "get_label")()
+ elm = getattr(
+ getattr(elm, f"get_{label_object}axis")(), "get_label"
+ )()
if elm != self.element:
try:
index += 1
- getattr(elm, "set_" + property_name, None)(v[index])
- except TypeError as err:
+ getattr(elm, "set_" + property_name, lambda x: None)(
+ v[index]
+ )
+ except TypeError:
pass
else:
elements.append(elm)
@@ -102,33 +133,69 @@ def set(v, v_list=None):
def getAll():
label_object = None
- if isinstance(self.element, Text) and len(main_figure(self.element).selection.targets) and isinstance(main_figure(self.element).selection.targets[0].target, Axes):
+ if (
+ isinstance(self.element, Text)
+ and len(main_figure(self.element).selection.targets)
+ and isinstance(
+ main_figure(self.element).selection.targets[0].target, Axes
+ )
+ ):
for elm in main_figure(self.element).selection.targets:
elm = elm.target
- if self.element == getattr(getattr(elm, f"get_xaxis")(), "get_label")():
+ if (
+ self.element
+ == getattr(getattr(elm, "get_xaxis")(), "get_label")()
+ ):
label_object = "x"
break
- if self.element == getattr(getattr(elm, f"get_yaxis")(), "get_label")():
+ if (
+ self.element
+ == getattr(getattr(elm, "get_yaxis")(), "get_label")()
+ ):
label_object = "y"
break
- values = [(self.element, property_name, getattr(self.element, "get_" + property_name)())]
- for index, elm in enumerate(main_figure(self.element).selection.targets):
+ values = [
+ (
+ self.element,
+ property_name,
+ getattr(self.element, "get_" + property_name)(),
+ )
+ ]
+ for index, elm in enumerate(
+ main_figure(self.element).selection.targets
+ ):
elm = elm.target
# special treatment for the xylabels, as they are not directly the target objects
if label_object is not None:
- elm = getattr(getattr(elm, f"get_{label_object}axis")(), "get_label")()
+ elm = getattr(
+ getattr(elm, f"get_{label_object}axis")(), "get_label"
+ )()
if elm != self.element:
try:
- values.append([elm, property_name, getattr(elm, "get_" + property_name, None)()])
- except TypeError as err:
+ values.append(
+ [
+ elm,
+ property_name,
+ getattr(
+ elm, "get_" + property_name, lambda: None
+ )(),
+ ]
+ )
+ except TypeError:
pass
return values
- self.setLinkedProperty = set # lambda text: getattr(self.element, "set_"+property_name)(text)
- self.getLinkedProperty = lambda: getattr(self.element, "get_" + property_name)()
+ self.setLinkedProperty = (
+ set # lambda text: getattr(self.element, "set_"+property_name)(text)
+ )
+ self.getLinkedProperty = lambda: getattr(
+ self.element, "get_" + property_name
+ )()
self.getLinkedPropertyAll = getAll
- self.serializeLinkedProperty = lambda x: ".set_" + property_name + "(%s)" % x
+ self.serializeLinkedProperty = (
+ lambda x: ".set_" + property_name + "(%s)" % x
+ )
if condition is None:
self.condition = lambda x: True
@@ -136,10 +203,11 @@ def getAll():
self.condition = condition
self.editingFinished.connect(self.updateLink)
- signal.connect(self.setTarget)
+ if signal is not None:
+ signal.connect(self.setTarget)
def setTarget(self, element: Artist):
- """ set the target for the widget """
+ """set the target for the widget"""
self.element = element
try:
self.set(self.getLinkedProperty())
@@ -149,8 +217,17 @@ def setTarget(self, element: Artist):
else:
self.show()
+ def setEnabled(self, enabled):
+ pass
+
+ def hide(self):
+ pass
+
+ def show(self):
+ pass
+
def updateLink(self):
- """ update the linked property """
+ """update the linked property"""
old_value = self.getLinkedPropertyAll()
try:
@@ -161,7 +238,7 @@ def updateLink(self):
new_value = self.getLinkedPropertyAll()
def save_change(element):
- if isinstance(element, mpl.figure.Figure):
+ if isinstance(element, Figure):
fig = element
else:
fig = main_figure(element)
@@ -169,19 +246,31 @@ def save_change(element):
if isinstance(element, Text):
fig.change_tracker.addNewTextChange(element)
else:
- fig.change_tracker.addChange(element, self.serializeLinkedProperty(self.getSerialized()))
+ fig.change_tracker.addChange(
+ element, self.serializeLinkedProperty(self.getSerialized(element))
+ )
+
+ if (
+ self.property_name == "xlim"
+ or self.property_name == "ylim"
+ or self.property_name == "xlabel"
+ or self.property_name == "ylabel"
+ ):
+ def save_change(element):
+ element.figure.change_tracker.addNewAxesChange(element)
def undo():
for elem, property_name, value in old_value:
- getattr(elem, "set_" + property_name, None)(value)
+ getattr(elem, "set_" + property_name, lambda x: None)(value)
save_change(elem)
+
def redo():
for elem, property_name, value in new_value:
- getattr(elem, "set_" + property_name, None)(value)
+ getattr(elem, "set_" + property_name, lambda x: None)(value)
save_change(elem)
element = elements[0]
- if isinstance(element, mpl.figure.Figure):
+ if isinstance(element, Figure):
fig = element
else:
fig = main_figure(element)
@@ -192,24 +281,24 @@ def redo():
main_figure(self.element).signals.figure_selection_property_changed.emit()
def set(self, value):
- """ set the value (to be overloaded) """
+ """set the value (to be overloaded)"""
pass
def get(self):
- """ get the value """
+ """get the value"""
return None
- def getSerialized(self):
- """ serialize the value for saving as a command """
+ def getSerialized(self, element):
+ """serialize the value for saving as a command"""
return ""
class FreeNumberInput(QtWidgets.QLineEdit):
send_signal = True
- valueChanged = QtCore.Signal(float)
+ valueChanged = Signal(float)
def __init__(self):
- """ Like a QSpinBox for number import, but without min or max range or a fixed resolution.
+ """Like a QSpinBox for number import, but without min or max range or a fixed resolution.
Especially important for the limits of logarithmic plots.
Attributes:
@@ -218,11 +307,11 @@ def __init__(self):
valueChanged : a signal that is emitted when the value is changed by the user
"""
QtWidgets.QLineEdit.__init__(self)
- #self.setMaximumWidth(50)
+ # self.setMaximumWidth(50)
self.textChanged.connect(self.emitValueChanged)
def emitValueChanged(self):
- """ connected to the textChanged signal """
+ """connected to the textChanged signal"""
if self.send_signal:
try:
value = self.value()
@@ -233,7 +322,7 @@ def emitValueChanged(self):
pass
def value(self) -> Optional[float]:
- """ return the value of the input field """
+ """return the value of the input field"""
try:
return float(self.text())
except ValueError:
@@ -243,7 +332,7 @@ def value(self) -> Optional[float]:
return None
def setValue(self, value: float):
- """ set the value of the input field """
+ """set the value of the input field"""
self.send_signal = False
try:
self.setText(str(value))
@@ -253,14 +342,21 @@ def setValue(self, value: float):
class DimensionsWidget(QtWidgets.QWidget, Linkable):
- valueChanged = QtCore.Signal(tuple)
- valueChangedX = QtCore.Signal(float)
- valueChangedY = QtCore.Signal(float)
+ valueChanged = Signal(tuple)
+ valueChangedX = Signal(float)
+ valueChangedY = Signal(float)
transform = None
noSignal = False
- def __init__(self, layout: QtWidgets.QLayout, text: str, join: str, unit: str, free: bool = False):
- """ a widget that lets the user input a pair of dimensions (e.g. widh and height)
+ def __init__(
+ self,
+ layout: QtWidgets.QLayout,
+ text: str,
+ join: str,
+ unit: str,
+ free: bool = False,
+ ):
+ """a widget that lets the user input a pair of dimensions (e.g. widh and height)
Args:
layout: the layout to which to add the widget
@@ -271,10 +367,10 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, join: str, unit: str, f
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
self.text = QtWidgets.QLabel(text)
- self.layout.addWidget(self.text)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main.addWidget(self.text)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
if free:
self.input1 = FreeNumberInput()
@@ -286,11 +382,11 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, join: str, unit: str, f
self.input1.setMinimum(-99999)
self.input1.setMaximumWidth(100)
self.input1.valueChanged.connect(self.onValueChangedX)
- self.layout.addWidget(self.input1)
+ self.layout_main.addWidget(self.input1)
self.text2 = QtWidgets.QLabel(join)
self.text2.setMaximumWidth(self.text2.fontMetrics().width(join))
- self.layout.addWidget(self.text2)
+ self.layout_main.addWidget(self.text2)
if free:
self.input2 = FreeNumberInput()
@@ -302,42 +398,42 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, join: str, unit: str, f
self.input2.setMinimum(-99999)
self.input2.setMaximumWidth(100)
self.input2.valueChanged.connect(self.onValueChangedY)
- self.layout.addWidget(self.input2)
+ self.layout_main.addWidget(self.input2)
self.editingFinished = self.valueChanged
def setLabel(self, text: str):
- """ set the text of the label """
+ """set the text of the label"""
self.text.setText(text)
def setUnit(self, unit: str):
- """ Sets the text for the unit for the values """
+ """Sets the text for the unit for the values"""
self.input1.setSuffix(" " + unit)
self.input2.setSuffix(" " + unit)
- def setTransform(self, transform: mpl.transforms.Transform):
- """ set the transform for the units """
+ def setTransform(self, transform: Transform):
+ """set the transform for the units"""
self.transform = transform
def onValueChangedX(self):
- """ called when the value was changed -> emit the value changed signal """
+ """called when the value was changed -> emit the value changed signal"""
if not self.noSignal:
self.valueChangedX.emit(self.value()[0])
self.valueChanged.emit(tuple(self.value()))
def onValueChangedY(self):
- """ called when the value was changed -> emit the value changed signal """
+ """called when the value was changed -> emit the value changed signal"""
if not self.noSignal:
self.valueChangedY.emit(self.value()[1])
self.valueChanged.emit(tuple(self.value()))
def onValueChanged(self):
- """ called when the value was changed -> emit the value changed signal """
+ """called when the value was changed -> emit the value changed signal"""
if not self.noSignal:
self.valueChanged.emit(tuple(self.value()))
def setValue(self, values: tuple, signal=False):
- """ set the two values """
+ """set the two values"""
self.noSignal = True
if self.transform:
values = self.transform.transform(values)
@@ -348,32 +444,42 @@ def setValue(self, values: tuple, signal=False):
self.onValueChanged()
def value(self):
- """ get the value """
+ """get the value"""
tuple = (self.input1.value(), self.input2.value())
if self.transform:
tuple = self.transform.inverted().transform(tuple)
return tuple
def get(self) -> tuple:
- """ get the value (used for the Linkable parent class) """
+ """get the value (used for the Linkable parent class)"""
return self.value()
def set(self, value: tuple):
- """ set both values (used for the Linkable parent class) """
+ """set both values (used for the Linkable parent class)"""
self.setValue(value)
- def getSerialized(self) -> str:
- """ serialize the values """
+ def getSerialized(self, element) -> str:
+ """serialize the values"""
+ return ", ".join(
+ [str(i) for i in getattr(element, f"get_{self.property_name}")()]
+ )
return ", ".join([str(i) for i in self.get()])
class TextWidget(QtWidgets.QWidget, Linkable):
- editingFinished = QtCore.Signal()
+ editingFinished = Signal()
noSignal = False
last_text = None
- def __init__(self, layout: QtWidgets.QLayout, text: str, multiline: bool = False, horizontal: bool = True, allow_literal_decoding=False):
- """ a text input widget with a label.
+ def __init__(
+ self,
+ layout: QtWidgets.QLayout,
+ text: str,
+ multiline: bool = False,
+ horizontal: bool = True,
+ allow_literal_decoding=False,
+ ):
+ """a text input widget with a label.
Args:
layout: the layout to which to add the widget
@@ -385,12 +491,14 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, multiline: bool = False
layout.addWidget(self)
self.allow_literal_decoding = allow_literal_decoding
if horizontal:
- self.layout = QtWidgets.QHBoxLayout(self)
+ layout_obj = QtWidgets.QHBoxLayout(self)
else:
- self.layout = QtWidgets.QVBoxLayout(self)
+ layout_obj = QtWidgets.QVBoxLayout(self)
+ self.setLayout(layout_obj)
+ self.layout_main = layout_obj
self.label = QtWidgets.QLabel(text)
- self.layout.addWidget(self.label)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main.addWidget(self.label)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.multiline = multiline
if multiline:
@@ -400,19 +508,19 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, multiline: bool = False
else:
self.input1 = QtWidgets.QLineEdit()
self.input1.editingFinished.connect(self.valueChangeEvent)
- self.layout.addWidget(self.input1)
+ self.layout_main.addWidget(self.input1)
def valueChangeEvent(self):
- """ an event that is triggered when the text in the input field is changed """
+ """an event that is triggered when the text in the input field is changed"""
if not self.noSignal and self.input1.text() != self.last_text:
self.editingFinished.emit()
def setLabel(self, text: str):
- """ set the text of the label """
+ """set the text of the label"""
self.label.setLabel(text)
def setText(self, text: str, signal=False):
- """ set contents of the text input widget """
+ """set contents of the text input widget"""
self.noSignal = True
text = text.replace("\n", "\\n")
self.last_text = text
@@ -425,12 +533,13 @@ def setText(self, text: str, signal=False):
self.editingFinished.emit()
def text(self) -> str:
- """ return the text """
+ """return the text"""
text = self.input1.text()
return text.replace("\\n", "\n")
def get(self) -> str:
import ast
+
""" get the value (used for the Linkable parent class) """
if self.allow_literal_decoding:
try:
@@ -440,19 +549,26 @@ def get(self) -> str:
return self.text()
def set(self, value: str):
- """ set the value (used for the Linkable parent class) """
+ """set the value (used for the Linkable parent class)"""
self.setText(str(value))
- def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
- return "\"" + str(self.get()) + "\""
+ def getSerialized(self, element) -> str:
+ """serialize the value (used for the Linkable parent class)"""
+ return '"' + str(self.get()) + '"'
+
class NumberWidget(QtWidgets.QWidget, Linkable):
- editingFinished = QtCore.Signal()
+ editingFinished = Signal()
noSignal = False
- def __init__(self, layout: QtWidgets.QLayout, text: str, min: float = None, use_float: bool = True):
- """ A spin box with a label next to it.
+ def __init__(
+ self,
+ layout: QtWidgets.QLayout,
+ text: str,
+ min: float | None = None,
+ use_float: bool = True,
+ ):
+ """A spin box with a label next to it.
Args:
layout: the layout to which to add the widget
@@ -462,10 +578,10 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, min: float = None, use_
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
self.label = QtWidgets.QLabel(text)
- self.layout.addWidget(self.label)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main.addWidget(self.label)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.type = float if use_float else int
if use_float is False:
@@ -475,19 +591,19 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, min: float = None, use_
if min is not None:
self.input1.setMinimum(min)
self.input1.valueChanged.connect(self.valueChangeEvent)
- self.layout.addWidget(self.input1)
+ self.layout_main.addWidget(self.input1)
def valueChangeEvent(self):
- """ when the value of the spin box changes """
+ """when the value of the spin box changes"""
if not self.noSignal:
self.editingFinished.emit()
def setLabel(self, text: str):
- """ set the text label """
+ """set the text label"""
self.label.setLabel(text)
def setValue(self, text: float, signal=False):
- """ set the value of the spin box """
+ """set the value of the spin box"""
self.noSignal = True
self.input1.setValue(text)
self.noSignal = False
@@ -495,29 +611,29 @@ def setValue(self, text: float, signal=False):
self.editingFinished.emit()
def value(self) -> float:
- """ get the value of the spin box """
+ """get the value of the spin box"""
text = self.input1.value()
return text
def get(self) -> float:
- """ get the value (used for the Linkable parent class) """
+ """get the value (used for the Linkable parent class)"""
return self.value()
def set(self, value: float):
- """ set the value (used for the Linkable parent class) """
+ """set the value (used for the Linkable parent class)"""
self.setValue(value)
- def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
+ def getSerialized(self, element) -> str:
+ """serialize the value (used for the Linkable parent class)"""
return str(self.get())
class ComboWidget(QtWidgets.QWidget, Linkable):
- editingFinished = QtCore.Signal()
+ editingFinished = Signal()
noSignal = False
def __init__(self, layout: QtWidgets.QLayout, text: str, values: Sequence):
- """ A combo box widget with a label
+ """A combo box widget with a label
Args:
layout: the layout to which to add the widget
@@ -526,31 +642,31 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, values: Sequence):
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
self.label = QtWidgets.QLabel(text)
- self.layout.addWidget(self.label)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main.addWidget(self.label)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.values = values
self.input1 = QtWidgets.QComboBox()
self.input1.addItems(values)
- self.layout.addWidget(self.input1)
+ self.layout_main.addWidget(self.input1)
self.input1.currentIndexChanged.connect(self.valueChangeEvent)
- self.layout.addWidget(self.input1)
+ self.layout_main.addWidget(self.input1)
def valueChangeEvent(self):
- """ called when the value has changed """
+ """called when the value has changed"""
if not self.noSignal:
self.editingFinished.emit()
def setLabel(self, text: str):
- """ set the text of the label """
+ """set the text of the label"""
self.label.setLabel(text)
def setText(self, text: str, signal=False):
- """ set the value of the combo box """
+ """set the value of the combo box"""
self.noSignal = True
index = self.values.index(text)
self.input1.setCurrentIndex(index)
@@ -559,30 +675,30 @@ def setText(self, text: str, signal=False):
self.editingFinished.emit()
def text(self) -> str:
- """ get the value of the combo box """
+ """get the value of the combo box"""
index = self.input1.currentIndex()
return self.values[index]
def get(self) -> str:
- """ get the value (used for the Linkable parent class) """
+ """get the value (used for the Linkable parent class)"""
return self.text()
def set(self, value: str):
- """ set the value (used for the Linkable parent class) """
+ """set the value (used for the Linkable parent class)"""
self.setText(value)
- def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
- return "\"" + str(self.get()) + "\""
+ def getSerialized(self, element) -> str:
+ """serialize the value (used for the Linkable parent class)"""
+ return '"' + str(self.get()) + '"'
class CheckWidget(QtWidgets.QWidget, Linkable):
- editingFinished = QtCore.Signal()
- stateChanged = QtCore.Signal(int)
+ editingFinished = Signal()
+ stateChanged = Signal(int)
noSignal = False
- def __init__(self, layout: QtWidgets.QLabel, text: str):
- """ a widget that contains a checkbox with a label
+ def __init__(self, layout: QtWidgets.QLayout, text: str):
+ """a widget that contains a checkbox with a label
Args:
layout: the layout to which to add the widget
@@ -590,24 +706,24 @@ def __init__(self, layout: QtWidgets.QLabel, text: str):
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
self.label = QtWidgets.QLabel(text)
- self.layout.addWidget(self.label)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main.addWidget(self.label)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.input1 = QtWidgets.QCheckBox()
self.input1.setTristate(False)
self.input1.stateChanged.connect(self.onStateChanged)
- self.layout.addWidget(self.input1)
+ self.layout_main.addWidget(self.input1)
def onStateChanged(self):
- """ when the state of the checkbox changes """
+ """when the state of the checkbox changes"""
if not self.noSignal:
self.stateChanged.emit(self.input1.isChecked())
self.editingFinished.emit()
def setChecked(self, state: bool, signal=False):
- """ set the value of the check box """
+ """set the value of the check box"""
self.noSignal = True
self.input1.setChecked(state)
self.noSignal = False
@@ -616,28 +732,28 @@ def setChecked(self, state: bool, signal=False):
self.editingFinished.emit()
def isChecked(self) -> bool:
- """ get the value of the checkbox """
+ """get the value of the checkbox"""
return self.input1.isChecked()
def get(self) -> bool:
- """ set the value (used for the Linkable parent class) """
+ """set the value (used for the Linkable parent class)"""
return self.isChecked()
def set(self, value: bool):
- """ get the value (used for the Linkable parent class) """
+ """get the value (used for the Linkable parent class)"""
self.setChecked(value)
- def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
+ def getSerialized(self, element) -> str:
+ """serialize the value (used for the Linkable parent class)"""
return "True" if self.get() else "False"
class RadioWidget(QtWidgets.QWidget):
- stateChanged = QtCore.Signal(int, str)
+ stateChanged = Signal(int, str)
noSignal = False
def __init__(self, layout: QtWidgets.QLayout, texts: Sequence[str]):
- """ a group of radio buttons
+ """a group of radio buttons
Args:
layout: the layout to which to add the widget
@@ -645,8 +761,8 @@ def __init__(self, layout: QtWidgets.QLayout, texts: Sequence[str]):
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.radio_buttons = []
@@ -655,19 +771,21 @@ def __init__(self, layout: QtWidgets.QLayout, texts: Sequence[str]):
for name in texts:
radio = QtWidgets.QRadioButton(name)
radio.toggled.connect(self.onToggled)
- self.layout.addWidget(radio)
+ self.layout_main.addWidget(radio)
self.radio_buttons.append(radio)
self.radio_buttons[0].setChecked(True)
def onToggled(self, checked: int):
- """ called when a radio button is toggled """
+ """called when a radio button is toggled"""
if checked:
- self.checked = np.argmax([radio.isChecked() for radio in self.radio_buttons])
+ self.checked = np.argmax(
+ [radio.isChecked() for radio in self.radio_buttons]
+ )
if not self.noSignal:
self.stateChanged.emit(self.checked, self.texts[self.checked])
def setState(self, state: int):
- """ set the state of the widget """
+ """set the state of the widget"""
self.noSignal = True
for index, radio in enumerate(self.radio_buttons):
radio.setChecked(state == index)
@@ -675,15 +793,20 @@ def setState(self, state: int):
self.noSignal = False
def getState(self) -> int:
- """ get the state of the widget """
+ """get the state of the widget"""
return self.checked
class QColorWidget(QtWidgets.QWidget, Linkable):
- valueChanged = QtCore.Signal(str)
+ valueChanged = Signal(str)
- def __init__(self, layout: QtWidgets.QLayout, text: str = None, value: str = None):
- """ A colored button what acts as an color input
+ def __init__(
+ self,
+ layout: QtWidgets.QLayout,
+ text: str | None = None,
+ value: str | None = None,
+ ):
+ """A colored button what acts as an color input
Args:
layout: the layout to which to add the widget
@@ -691,16 +814,16 @@ def __init__(self, layout: QtWidgets.QLayout, text: str = None, value: str = Non
value: the value of the color widget
"""
super().__init__()
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self)
if text is not None:
self.label = QtWidgets.QLabel(text)
- self.layout.addWidget(self.label)
+ self.layout_main.addWidget(self.label)
self.button = QtWidgets.QPushButton()
- self.layout.addWidget(self.button)
+ self.layout_main.addWidget(self.button)
self.button.clicked.connect(self.OpenDialog)
# default value for the color
@@ -712,34 +835,41 @@ def __init__(self, layout: QtWidgets.QLayout, text: str = None, value: str = Non
self.editingFinished = self.valueChanged
def changeEvent(self, event):
- """ when the widget is enabled """
- if event.type() == QtCore.QEvent.EnabledChange:
+ """when the widget is enabled"""
+ if event.type() == QtCore.QEvent.Type.EnabledChange:
if not self.isEnabled():
self.button.setStyleSheet("background-color: #f0f0f0;")
else:
self.setColor(self.color)
def OpenDialog(self):
- """ open a color chooser dialog """
+ """open a color chooser dialog"""
+ parent = self.parent()
+ if parent is None or not isinstance(parent, QtWidgets.QWidget):
+ raise TypeError("parent is None")
# get new color from color picker
- self.current_color = QtGui.QColor(*tuple(int(x) for x in mpl.colors.to_rgba_array(self.getColor())[0] * 255))
- self.dialog = QtWidgets.QColorDialog(self.current_color, self.parent())
+ self.current_color = QtGui.QColor(
+ *tuple(int(x) for x in mpl.colors.to_rgba_array(self.getColor())[0] * 255)
+ )
+ self.dialog = QtWidgets.QColorDialog(self.current_color, parent)
self.dialog.setOptions(QtWidgets.QColorDialog.ShowAlphaChannel)
- for index, color in enumerate(plt.rcParams['axes.prop_cycle'].by_key()['color']):
+ for index, color in enumerate(
+ plt.rcParams["axes.prop_cycle"].by_key()["color"]
+ ):
self.dialog.setCustomColor(index, QtGui.QColor(color))
self.dialog.open(self.dialog_finished)
self.dialog.currentColorChanged.connect(self.dialog_changed)
self.dialog.rejected.connect(self.dialog_rejected)
def dialog_rejected(self):
- """ called when the dialog is cancelled """
+ """called when the dialog is cancelled"""
color = self.current_color
color = color.name() + "%0.2x" % color.alpha()
self.setColor(color)
self.valueChanged.emit(self.color)
def dialog_changed(self):
- """ called when the value in the dialog changes """
+ """called when the value in the dialog changes"""
color = self.dialog.currentColor()
# if a color is set, apply it
if color.isValid():
@@ -748,7 +878,7 @@ def dialog_changed(self):
self.valueChanged.emit(self.color)
def dialog_finished(self):
- """ called when the dialog is finished with a click on 'ok' """
+ """called when the dialog is finished with a click on 'ok'"""
color = self.dialog.selectedColor()
self.dialog = None
# if a color is set, apply it
@@ -757,30 +887,39 @@ def dialog_finished(self):
self.setColor(color)
self.valueChanged.emit(self.color)
- def setColor(self, value: str):
- """ set the color """
+ def setColor(self, value: str | None):
+ """set the color"""
# display and save the new color
if value is None:
value = "#FF0000FF"
- self.button.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
+ self.button.setAttribute(
+ QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True
+ )
if len(value) == 9:
- self.button.setStyleSheet("background-color: rgba(%d, %d, %d, %d%%);" % (
- int(value[1:3], 16), int(value[3:5], 16), int(value[5:7], 16), int(value[7:], 16) * 100 / 255))
+ self.button.setStyleSheet(
+ "background-color: rgba(%d, %d, %d, %d%%);"
+ % (
+ int(value[1:3], 16),
+ int(value[3:5], 16),
+ int(value[5:7], 16),
+ int(value[7:], 16) * 100 / 255,
+ )
+ )
else:
self.button.setStyleSheet("background-color: %s;" % (value,))
self.color = value
def getColor(self) -> str:
- """ get the color value """
+ """get the color value"""
# return the color
return self.color
def get(self):
- """ get the value (used for the Linkable parent class) """
+ """get the value (used for the Linkable parent class)"""
return self.getColor()
def set(self, value):
- """ set the value (used for the Linkable parent class) """
+ """set the value (used for the Linkable parent class)"""
try:
if len(value) == 4:
self.setColor(mpl.colors.to_hex(value) + "%02X" % int(value[-1] * 255))
@@ -789,6 +928,6 @@ def set(self, value):
except ValueError:
self.setColor(None)
- def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
- return "\"" + self.color + "\""
+ def getSerialized(self, element) -> str:
+ """serialize the value (used for the Linkable parent class)"""
+ return '"' + self.color + '"'
diff --git a/pylustrator/QtGui.py b/pylustrator/QtGui.py
index c1213a5..71e15c8 100644
--- a/pylustrator/QtGui.py
+++ b/pylustrator/QtGui.py
@@ -1,442 +1,502 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# QtGui.py
-
-# Copyright (c) 2016-2020, Richard Gerum
-#
-# This file is part of Pylustrator.
-#
-# Pylustrator is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pylustrator is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pylustrator. If not, see
-
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-
-import numpy as np
-import matplotlib.pyplot as plt
-try: # for matplotlib > 3.0
- from matplotlib.backends.backend_qtagg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
-except ModuleNotFoundError:
- from matplotlib.backends.backend_qt5agg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
-from pylustrator.components.matplotlibwidget import MatplotlibWidget
-from matplotlib import _pylab_helpers
-from matplotlib.figure import Figure
-from matplotlib.artist import Artist
-import matplotlib as mpl
-import qtawesome as qta
-
-from .QtShortCuts import QDragableColor
-
-import sys
-
-
-def my_excepthook(type, value, tback):
- sys.__excepthook__(type, value, tback)
-
-
-sys.excepthook = my_excepthook
-
-""" Matplotlib overload """
-figures = {}
-app = None
-
-
-def initialize():
- """ patch figure and show to display the color chooser GUI """
- global app
- if app is None:
- app = QtWidgets.QApplication(sys.argv)
- plt.show = show
- plt.figure = figure
-
-
-def show():
- """ the patched show to display the color choose gui """
- global figures
- # iterate over figures
- for figure in figures:
- # get the window
- window = figures[figure].window
- # and show it
- window.show()
- # execute the application
- app.exec_()
-
-
-def figure(num=None, figsize=None, *args, **kwargs):
- """ the patched figure to initialize to color chooser GUI """
- global figures
- # if num is not defined create a new number
- if num is None:
- num = len(figures)
- # if number is not defined
- if num not in figures.keys():
- # create a new window and store it
- canvas = PlotWindow(num, *args, **kwargs).canvas
- figures[num] = canvas
- # get the canvas of the figure
- canvas = figures[num]
- # set the size if it is defined
- if figsize is not None:
- figures[num].window.setGeometry(100, 100, figsize[0] * 80, figsize[1] * 80)
- # set the figure as the active figure
- _pylab_helpers.Gcf.set_active(canvas.manager)
- # return the figure
- return canvas.figure
-
-
-""" Figure list functions """
-
-
-def addChildren(color_artists: list, parent: Artist):
- """ find all the children of an Artist that use a color """
- for artist in parent.get_children():
- # ignore empty texts
- if isinstance(artist, mpl.text.Text) and artist.get_text() == "":
- continue
-
- # omit the helper objects generated by pylustrator
- if getattr(artist, "_no_save", False):
- continue
-
- # add the children of the item (not for text or ticks)
- if not isinstance(artist, (mpl.text.Text, mpl.axis.XTick, mpl.axis.YTick)):
- addChildren(color_artists, artist)
-
- # iterate over the elements
- for color_type_name in ["edgecolor", "facecolor", "color", "markeredgecolor", "markerfacecolor"]:
- colors = getattr(artist, "get_" + color_type_name, lambda: None)()
- # ignore colors that are not set
- if colors is None or len(colors) == 0:
- continue
-
- # convert to array
- if (not (isinstance(colors, np.ndarray) and len(colors.shape) > 1) and not isinstance(colors, list)) or \
- getattr(colors, "cmap", None) is not None:
- colors = [colors]
-
- # iterate over the colors
- for color in colors:
- # test if it is a colormap
- try:
- cmap = color.cmap
- value = color.value
- except AttributeError:
- cmap = None
-
- try:
- mpl.colors.to_hex(color)
- except ValueError:
- continue
-
- # omit blacks and whites
- if mpl.colors.to_hex(color) == "#000000" or mpl.colors.to_hex(color) == "#ffffff":
- continue
-
- # if we have a colormap
- if cmap:
- if getattr(cmap, "get_color", None):
- # iterate over the colors of the colormap
- for index, color in enumerate(cmap.get_color()):
- # convert to hex
- color = mpl.colors.to_hex(color)
- # check if it is already in the dictionary
- if color not in color_artists:
- color_artists[color] = []
- # add the artist
- color_artists[color].append([color_type_name, artist, value, cmap, index])
- else:
- # check if it is already in the dictionary
- if cmap not in color_artists:
- color_artists[cmap] = []
- color_artists[cmap].append([color_type_name, artist, value, cmap, value])
- else:
- # ignore transparent colors
- if mpl.colors.to_rgba(color)[3] == 0:
- continue
- # convert to hey
- color = mpl.colors.to_hex(color)
- # check if it is already in the dictionary
- if color not in color_artists:
- color_artists[color] = []
- # add the artist
- color_artists[color].append([color_type_name, artist, None, None, None])
-
-
-def figureListColors(figure: Figure):
- """ add all artist with colors to a list in the figure """
- figure.color_artists = {}
- addChildren(figure.color_artists, figure)
-
-
-def figureSwapColor(figure: Figure, new_color: str, color_base: str):
- """ swap two colors of a figure """
- if getattr(figure, "color_artists", None) is None:
- figureListColors(figure)
- changed_cmaps = []
- maps = plt.colormaps()
- for data in figure.color_artists[color_base]:
- # get the data
- color_type_name, artist, value, cmap, index = data
- # if the color is part of a colormap, update the colormap
- if cmap:
- # update colormap
- if cmap not in changed_cmaps:
- changed_cmaps.append(cmap)
- if getattr(cmap, "set_color", None) is not None:
- cmap.set_color(new_color, index)
- if getattr(cmap, "set_color", None) is None:
- if new_color in maps:
- cmap = plt.get_cmap(new_color)
- else:
- getattr(artist, "set_" + color_type_name)(new_color)
- artist.figure.change_tracker.addChange(artist,
- ".set_" + color_type_name + "(\"%s\")" % (new_color,))
- continue
- # use the attributes setter method
- getattr(artist, "set_" + color_type_name)(cmap(value))
- artist.figure.change_tracker.addChange(artist, ".set_" + color_type_name + "(plt.get_cmap(\"%s\")(%s))" % (
- cmap.name, str(value)))
- else:
- if new_color in maps:
- cmap = plt.get_cmap(new_color)
- getattr(artist, "set_" + color_type_name)(cmap(0))
- artist.figure.change_tracker.addChange(artist,
- ".set_" + color_type_name + "(plt.get_cmap(\"%s\")(%s))" % (
- cmap.name, str(0)))
- else:
- # use the attributes setter method
- getattr(artist, "set_" + color_type_name)(new_color)
- artist.figure.change_tracker.addChange(artist, ".set_" + color_type_name + "(\"%s\")" % (new_color,))
-
-
-""" Window """
-
-
-class ColorChooserWidget(QtWidgets.QWidget):
- trigger_no_update = False
-
- def __init__(self, parent: QtWidgets, canvas: FigureCanvas, signals: "Signals"=None):
- """ A widget to display all curently used colors and let the user switch them.
-
- Args:
- parent: the parent widget
- canvas: the figure's canvas element
- """
- QtWidgets.QWidget.__init__(self)
-
- # initialize color artist dict
- self.color_artists = {}
- # tracks how many colors have changed to make sure
- # that updateColorsText is only called after a
- # full swap this means 2 colors change
- self.swap_counter = 0
-
- # add update push button
- self.button_update = QtWidgets.QPushButton(qta.icon("ei.refresh"), "update")
- self.button_update.clicked.connect(self.updateColorsText)
-
- # add color chooser layout
- self.layout_right = QtWidgets.QVBoxLayout(self)
- self.layout_right.addWidget(self.button_update)
-
- self.layout_colors = QtWidgets.QVBoxLayout()
- self.layout_right.addLayout(self.layout_colors)
-
- self.layout_colors2 = QtWidgets.QVBoxLayout()
- self.layout_right.addLayout(self.layout_colors2)
-
- self.layout_buttons = QtWidgets.QVBoxLayout()
- self.layout_right.addLayout(self.layout_buttons)
- self.button_save = QtWidgets.QPushButton("Save Colors")
- self.button_save.clicked.connect(self.saveColors)
- self.layout_buttons.addWidget(self.button_save)
- self.button_load = QtWidgets.QPushButton("Load Colors")
- self.button_load.clicked.connect(self.loadColors)
- self.layout_buttons.addWidget(self.button_load)
-
- self.canvas = canvas
- #self.updateColors()
-
- # add a text widget to allow easy copy and paste
- self.colors_text_widget = QtWidgets.QTextEdit()
- self.colors_text_widget.setAcceptRichText(False)
- self.layout_colors2.addWidget(self.colors_text_widget)
- self.colors_text_widget.textChanged.connect(self.colorsTextChanged)
-
- if signals:
- signals.canvas_changed.connect(self.setCanvas)
-
- def setCanvas(self, canvas):
- self.canvas = canvas
-
- def saveColors(self):
- """ save the colors to a .txt file """
- options = QtWidgets.QFileDialog.Options()
- # options |= QtWidgets.QFileDialog.DontUseNativeDialog
-
- path = QtWidgets.QFileDialog.getSaveFileName(self, "Save Color File", getattr(self, "last_save_folder", None),"Text Files (*.txt);;All Files (*)", options=options)
-
- if isinstance(path, tuple):
- path = str(path[0])
- else:
- path = str(path)
- if not path:
- return
- self.last_save_folder = path
- with open(path, "w") as fp:
- fp.write(self.colors_text_widget.toPlainText())
-
- def loadColors(self):
- """ load a list of colors from a .txt file """
- options = QtWidgets.QFileDialog.Options()
- # options |= QtWidgets.QFileDialog.DontUseNativeDialog
-
- path = QtWidgets.QFileDialog.getOpenFileName(self, "Open Color File", getattr(self, "last_save_folder", None), "Text File (*.txt);;All Files (*)", options=options)
- if isinstance(path, tuple):
- path = str(path[0])
- else:
- path = str(path)
- # if path is empty
- if not path:
- return
-
- self.last_save_folder = path
- with open(path, "r") as fp:
- self.colors_text_widget.setText(fp.read())
-
- def addColorButton(self, color: str, basecolor: str = None):
- """ add a button for the given color """
- try:
- button = QDragableColor(mpl.colors.to_hex(color))
- except ValueError:
- button = QDragableColor(color)
- self.layout_colors.addWidget(button)
- button.color_changed.connect(lambda c: self.colorChanged(c, color_base=basecolor))
- button.color_changed_by_color_picker.connect(lambda e: self.resetSwapcounter(e))
- if basecolor:
- self.color_buttons[basecolor] = button
- self.color_buttons_list.append(button)
-
- def colorChanged(self, c, color_base):
- """ update a color when it is changed
- if colors are swapped then first change both colors
- and then update the text list of colors
- """
- self.color_selected(c, color_base)
-
- # call updateColorsText after 2 colors have swapped
- self.swap_counter += 1
- if self.swap_counter == 2:
- self.swap_counter = 0
- self.updateColorsText()
-
- def resetSwapcounter(self, _):
- """ when a color changed using the color picker the swap counter is reset """
- self.swap_counter = 0
- self.updateColorsText()
-
- def updateColorsText(self):
- """ update the text list of colors """
- # add recursively all artists of the figure
- figureListColors(self.canvas.figure)
- self.color_artists = list(self.canvas.figure.color_artists)
-
- # iterate over all colors
- self.color_buttons = {}
- self.color_buttons_list = []
-
- self.removeColoredButtons()
-
- for color in self.color_artists[:20]:
- self.addColorButton(color, color)
-
- self.trigger_no_update = True
- try:
- def colorToText(color):
- try:
- return mpl.colors.to_hex(color)
- except ValueError:
- return color
-
- self.colors_text_widget.setText("\n".join([colorToText(color) for color in self.color_artists[:10]]))
- finally:
- self.trigger_no_update = False
-
- # update the canvas dimensions
- self.canvas.updateGeometry()
-
- def colorsTextChanged(self):
- """ when the colors in the text widget changed
- after loading new colors or manually editing the text field
- """
- if self.trigger_no_update:
- return
-
- maps = plt.colormaps()
-
- # when the colors in the text edit changed
- for index, color in enumerate(self.colors_text_widget.toPlainText().split("\n")):
- try:
- color = mpl.colors.to_hex(color.strip())
- except ValueError:
- if color not in maps:
- continue
- if len(self.color_buttons_list) <= index:
- self.addColorButton(color)
- self.color_buttons_list[index].setColor(color)
-
- def color_selected(self, new_color: str, color_base: str):
- """ switch two colors """
- if color_base is None:
- return
- figureSwapColor(self.canvas.figure, new_color, color_base)
- # redraw the plot
- self.canvas.draw()
-
- def removeColoredButtons(self):
- while self.layout_colors.takeAt(0):
- pass
- self.color_buttons_list = []
-
-
-class PlotWindow(QtWidgets.QWidget):
- def __init__(self, number, *args, **kwargs):
- """ An alternative to the pylustrator gui that only displays the color chooser. Mainly for testing purpose. """
- QtWidgets.QWidget.__init__(self)
-
- # widget layout and elements
- self.setWindowTitle("Figure %s" % number)
- self.setWindowIcon(qta.icon("fa5.bar-chart"))
- self.layout_main = QtWidgets.QHBoxLayout(self)
-
- # add plot layout
- self.layout_plot = QtWidgets.QVBoxLayout(self)
- self.layout_main.addLayout(self.layout_plot)
-
- # add plot canvas
- self.canvas = MatplotlibWidget(self)
- self.canvas.window = self
- self.layout_plot.addWidget(self.canvas)
- _pylab_helpers.Gcf.set_active(self.canvas.manager)
-
- # add toolbar
- self.navi_toolbar = NavigationToolbar(self.canvas, self)
- self.layout_plot.addWidget(self.navi_toolbar)
- self.layout_plot.addStretch()
-
- self.colorWidget = ColorChooserWidget(self, self.canvas)
- self.layout_main.addWidget(self.colorWidget)
-
- def showEvent(self, a0: QtGui.QShowEvent):
- # update the colors
- self.colorWidget.updateColorsText()
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# QtGui.py
+
+# Copyright (c) 2016-2020, Richard Gerum
+#
+# This file is part of Pylustrator.
+#
+# Pylustrator is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pylustrator is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pylustrator. If not, see
+
+from typing import TYPE_CHECKING, Optional
+
+if TYPE_CHECKING:
+ from PyQt5 import QtGui, QtWidgets
+else:
+ from qtpy import QtGui, QtWidgets
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+try: # for matplotlib > 3.0
+ from matplotlib.backends.backend_qtagg import (
+ FigureCanvas, # ty:ignore[unresolved-import]
+ NavigationToolbar2QT as NavigationToolbar,
+ )
+except ModuleNotFoundError:
+ from matplotlib.backends.backend_qt5agg import (
+ FigureCanvas, # ty:ignore[unresolved-import]
+ NavigationToolbar2QT as NavigationToolbar,
+ )
+from pylustrator.components.matplotlibwidget import MatplotlibWidget
+from matplotlib import _pylab_helpers
+from matplotlib.figure import Figure
+from matplotlib.artist import Artist
+from matplotlib.text import Text
+from matplotlib.axis import XTick, YTick
+import matplotlib as mpl
+import qtawesome as qta
+
+from .QtShortCuts import QDragableColor
+
+import sys
+
+
+def my_excepthook(type, value, tback):
+ sys.__excepthook__(type, value, tback)
+
+
+sys.excepthook = my_excepthook
+
+""" Matplotlib overload """
+figures = {}
+app = None
+
+
+def initialize():
+ """patch figure and show to display the color chooser GUI"""
+ global app
+ if app is None:
+ app = QtWidgets.QApplication(sys.argv)
+ plt.show = show # ty:ignore[invalid-assignment]
+ plt.figure = figure # ty:ignore[invalid-assignment]
+
+
+def show():
+ """the patched show to display the color choose gui"""
+ global figures
+ # iterate over figures
+ for figure in figures:
+ # get the window
+ window = figures[figure].window
+ # and show it
+ window.show()
+ # execute the application
+ app.exec_() # ty:ignore[unresolved-attribute]
+
+
+def figure(num=None, figsize=None, *args, **kwargs):
+ """the patched figure to initialize to color chooser GUI"""
+ global figures
+ # if num is not defined create a new number
+ if num is None:
+ num = len(figures)
+ # if number is not defined
+ if num not in figures.keys():
+ # create a new window and store it
+ canvas = PlotWindow(num, *args, **kwargs).canvas
+ figures[num] = canvas
+ # get the canvas of the figure
+ canvas = figures[num]
+ # set the size if it is defined
+ if figsize is not None:
+ figures[num].window.setGeometry(100, 100, figsize[0] * 80, figsize[1] * 80)
+ # set the figure as the active figure
+ _pylab_helpers.Gcf.set_active(canvas.manager)
+ # return the figure
+ return canvas.figure
+
+
+""" Figure list functions """
+
+
+def addChildren(color_artists: dict, parent: Artist):
+ """find all the children of an Artist that use a color"""
+ for artist in parent.get_children():
+ # ignore empty texts
+ if isinstance(artist, Text) and artist.get_text() == "":
+ continue
+
+ # omit the helper objects generated by pylustrator
+ if getattr(artist, "_no_save", False):
+ continue
+
+ # add the children of the item (not for text or ticks)
+ if not isinstance(artist, (Text, XTick, YTick)):
+ addChildren(color_artists, artist)
+
+ # iterate over the elements
+ for color_type_name in [
+ "edgecolor",
+ "facecolor",
+ "color",
+ "markeredgecolor",
+ "markerfacecolor",
+ ]:
+ colors = getattr(artist, "get_" + color_type_name, lambda: None)()
+ # ignore colors that are not set
+ if colors is None or len(colors) == 0:
+ continue
+
+ # convert to array
+ if (
+ not (isinstance(colors, np.ndarray) and len(colors.shape) > 1)
+ and not isinstance(colors, list)
+ ) or getattr(colors, "cmap", None) is not None:
+ colors = [colors]
+
+ # iterate over the colors
+ for color in colors:
+ # test if it is a colormap
+ try:
+ cmap = color.cmap
+ value = color.value
+ except AttributeError:
+ cmap = None
+
+ try:
+ mpl.colors.to_hex(color)
+ except ValueError:
+ continue
+
+ # omit blacks and whites
+ if (
+ mpl.colors.to_hex(color) == "#000000"
+ or mpl.colors.to_hex(color) == "#ffffff"
+ ):
+ continue
+
+ # if we have a colormap
+ if cmap:
+ if getattr(cmap, "get_color", None):
+ # iterate over the colors of the colormap
+ for index, color in enumerate(cmap.get_color()):
+ # convert to hex
+ color = mpl.colors.to_hex(color)
+ # check if it is already in the dictionary
+ if color not in color_artists:
+ color_artists[color] = []
+ # add the artist
+ color_artists[color].append(
+ [color_type_name, artist, value, cmap, index]
+ )
+ else:
+ # check if it is already in the dictionary
+ if cmap not in color_artists:
+ color_artists[cmap] = []
+ color_artists[cmap].append(
+ [color_type_name, artist, value, cmap, value]
+ )
+ else:
+ # ignore transparent colors
+ if mpl.colors.to_rgba(color)[3] == 0:
+ continue
+ # convert to hey
+ color = mpl.colors.to_hex(color)
+ # check if it is already in the dictionary
+ if color not in color_artists:
+ color_artists[color] = []
+ # add the artist
+ color_artists[color].append(
+ [color_type_name, artist, None, None, None]
+ )
+
+
+def figureListColors(figure: Figure):
+ """add all artist with colors to a list in the figure"""
+ figure.color_artists = {} # type: ignore[attr-defined]
+ addChildren(figure.color_artists, figure) # ty:ignore[invalid-argument-type, unresolved-attribute]
+
+
+def figureSwapColor(figure: Figure, new_color: str, color_base: str):
+ """swap two colors of a figure"""
+ if getattr(figure, "color_artists", None) is None:
+ figureListColors(figure)
+ changed_cmaps = []
+ maps = plt.colormaps()
+ for data in figure.color_artists[color_base]: # ty:ignore[unresolved-attribute]
+ # get the data
+ color_type_name, artist, value, cmap, index = data
+ # if the color is part of a colormap, update the colormap
+ if cmap:
+ # update colormap
+ if cmap not in changed_cmaps:
+ changed_cmaps.append(cmap)
+ if getattr(cmap, "set_color", None) is not None:
+ cmap.set_color(new_color, index)
+ if getattr(cmap, "set_color", None) is None:
+ if new_color in maps:
+ cmap = plt.get_cmap(new_color)
+ else:
+ getattr(artist, "set_" + color_type_name)(new_color)
+ artist.figure.change_tracker.addChange(
+ artist, ".set_" + color_type_name + '("%s")' % (new_color,)
+ )
+ continue
+ # use the attributes setter method
+ getattr(artist, "set_" + color_type_name)(cmap(value))
+ artist.figure.change_tracker.addChange(
+ artist,
+ ".set_"
+ + color_type_name
+ + '(plt.get_cmap("%s")(%s))' % (cmap.name, str(value)),
+ )
+ else:
+ if new_color in maps:
+ cmap = plt.get_cmap(new_color)
+ getattr(artist, "set_" + color_type_name)(cmap(0))
+ artist.figure.change_tracker.addChange(
+ artist,
+ ".set_"
+ + color_type_name
+ + '(plt.get_cmap("%s")(%s))' % (cmap.name, str(0)),
+ )
+ else:
+ # use the attributes setter method
+ getattr(artist, "set_" + color_type_name)(new_color)
+ artist.figure.change_tracker.addChange(
+ artist, ".set_" + color_type_name + '("%s")' % (new_color,)
+ )
+
+
+""" Window """
+
+
+class ColorChooserWidget(QtWidgets.QWidget):
+ trigger_no_update = False
+
+ def __init__(self, parent: QtWidgets, canvas: FigureCanvas, signals=None): # ty:ignore[invalid-type-form]
+ """A widget to display all curently used colors and let the user switch them.
+
+ Args:
+ parent: the parent widget
+ canvas: the figure's canvas element
+ """
+ QtWidgets.QWidget.__init__(self)
+
+ # initialize color artist dict
+ self.color_artists = {}
+ # tracks how many colors have changed to make sure
+ # that updateColorsText is only called after a
+ # full swap this means 2 colors change
+ self.swap_counter = 0
+
+ # add update push button
+ self.button_update = QtWidgets.QPushButton(qta.icon("ei.refresh"), "update")
+ self.button_update.clicked.connect(self.updateColorsText)
+
+ # add color chooser layout
+ self.layout_right = QtWidgets.QVBoxLayout(self)
+ self.layout_right.addWidget(self.button_update)
+
+ self.layout_colors = QtWidgets.QVBoxLayout()
+ self.layout_right.addLayout(self.layout_colors)
+
+ self.layout_colors2 = QtWidgets.QVBoxLayout()
+ self.layout_right.addLayout(self.layout_colors2)
+
+ self.layout_buttons = QtWidgets.QVBoxLayout()
+ self.layout_right.addLayout(self.layout_buttons)
+ self.button_save = QtWidgets.QPushButton("Save Colors")
+ self.button_save.clicked.connect(self.saveColors)
+ self.layout_buttons.addWidget(self.button_save)
+ self.button_load = QtWidgets.QPushButton("Load Colors")
+ self.button_load.clicked.connect(self.loadColors)
+ self.layout_buttons.addWidget(self.button_load)
+
+ self.canvas = canvas
+ # self.updateColors()
+
+ # add a text widget to allow easy copy and paste
+ self.colors_text_widget = QtWidgets.QTextEdit()
+ self.colors_text_widget.setAcceptRichText(False)
+ self.layout_colors2.addWidget(self.colors_text_widget)
+ self.colors_text_widget.textChanged.connect(self.colorsTextChanged)
+
+ if signals:
+ signals.canvas_changed.connect(self.setCanvas)
+
+ def setCanvas(self, canvas):
+ self.canvas = canvas
+
+ def saveColors(self):
+ """save the colors to a .txt file"""
+ options = QtWidgets.QFileDialog.Options()
+ # options |= QtWidgets.QFileDialog.DontUseNativeDialog
+
+ path = QtWidgets.QFileDialog.getSaveFileName(
+ self,
+ "Save Color File",
+ getattr(self, "last_save_folder", None),
+ "Text Files (*.txt);;All Files (*)",
+ options=options,
+ )
+
+ if isinstance(path, tuple):
+ path = str(path[0])
+ else:
+ path = str(path)
+ if not path:
+ return
+ self.last_save_folder = path
+ with open(path, "w") as fp:
+ fp.write(self.colors_text_widget.toPlainText())
+
+ def loadColors(self):
+ """load a list of colors from a .txt file"""
+ options = QtWidgets.QFileDialog.Options()
+ # options |= QtWidgets.QFileDialog.DontUseNativeDialog
+
+ path = QtWidgets.QFileDialog.getOpenFileName(
+ self,
+ "Open Color File",
+ getattr(self, "last_save_folder", None),
+ "Text File (*.txt);;All Files (*)",
+ options=options,
+ )
+ if isinstance(path, tuple):
+ path = str(path[0])
+ else:
+ path = str(path)
+ # if path is empty
+ if not path:
+ return
+
+ self.last_save_folder = path
+ with open(path, "r") as fp:
+ self.colors_text_widget.setText(fp.read())
+
+ def addColorButton(self, color: str, basecolor: Optional[str] = None):
+ """add a button for the given color"""
+ try:
+ button = QDragableColor(mpl.colors.to_hex(color))
+ except ValueError:
+ button = QDragableColor(color)
+ self.layout_colors.addWidget(button)
+ button.color_changed.connect(
+ lambda c: self.colorChanged(c, color_base=basecolor)
+ )
+ button.color_changed_by_color_picker.connect(lambda e: self.resetSwapcounter(e))
+ if basecolor:
+ self.color_buttons[basecolor] = button
+ self.color_buttons_list.append(button)
+
+ def colorChanged(self, c, color_base):
+ """update a color when it is changed
+ if colors are swapped then first change both colors
+ and then update the text list of colors
+ """
+ self.color_selected(c, color_base)
+
+ # call updateColorsText after 2 colors have swapped
+ self.swap_counter += 1
+ if self.swap_counter == 2:
+ self.swap_counter = 0
+ self.updateColorsText()
+
+ def resetSwapcounter(self, _):
+ """when a color changed using the color picker the swap counter is reset"""
+ self.swap_counter = 0
+ self.updateColorsText()
+
+ def updateColorsText(self):
+ """update the text list of colors"""
+ # add recursively all artists of the figure
+ figureListColors(self.canvas.figure)
+ self.color_artists = list(self.canvas.figure.color_artists)
+
+ # iterate over all colors
+ self.color_buttons = {}
+ self.color_buttons_list = []
+
+ self.removeColoredButtons()
+
+ for color in self.color_artists[:20]:
+ self.addColorButton(color, color)
+
+ self.trigger_no_update = True
+ try:
+
+ def colorToText(color):
+ try:
+ return mpl.colors.to_hex(color)
+ except ValueError:
+ return color
+
+ self.colors_text_widget.setText(
+ "\n".join([colorToText(color) for color in self.color_artists[:10]])
+ )
+ finally:
+ self.trigger_no_update = False
+
+ # update the canvas dimensions
+ self.canvas.updateGeometry()
+
+ def colorsTextChanged(self):
+ """when the colors in the text widget changed
+ after loading new colors or manually editing the text field
+ """
+ if self.trigger_no_update:
+ return
+
+ maps = plt.colormaps()
+
+ # when the colors in the text edit changed
+ for index, color in enumerate(
+ self.colors_text_widget.toPlainText().split("\n")
+ ):
+ try:
+ color = mpl.colors.to_hex(color.strip())
+ except ValueError:
+ if color not in maps:
+ continue
+ if len(self.color_buttons_list) <= index:
+ self.addColorButton(color)
+ self.color_buttons_list[index].setColor(color)
+
+ def color_selected(self, new_color: str, color_base: str):
+ """switch two colors"""
+ if color_base is None:
+ return
+ figureSwapColor(self.canvas.figure, new_color, color_base)
+ # redraw the plot
+ self.canvas.draw()
+
+ def removeColoredButtons(self):
+ while self.layout_colors.takeAt(0):
+ pass
+ self.color_buttons_list = []
+
+
+class PlotWindow(QtWidgets.QWidget):
+ def __init__(self, number, *args, **kwargs):
+ """An alternative to the pylustrator gui that only displays the color chooser. Mainly for testing purpose."""
+ QtWidgets.QWidget.__init__(self)
+
+ # widget layout and elements
+ self.setWindowTitle("Figure %s" % number)
+ self.setWindowIcon(qta.icon("fa5.bar-chart"))
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+
+ # add plot layout
+ self.layout_plot = QtWidgets.QVBoxLayout(self)
+ self.layout_main.addLayout(self.layout_plot)
+
+ # add plot canvas
+ self.canvas = MatplotlibWidget(self)
+ self.canvas.window = self
+ self.layout_plot.addWidget(self.canvas)
+ _pylab_helpers.Gcf.set_active(self.canvas.manager)
+
+ # add toolbar
+ self.navi_toolbar = NavigationToolbar(self.canvas, self)
+ self.layout_plot.addWidget(self.navi_toolbar)
+ self.layout_plot.addStretch()
+
+ self.colorWidget = ColorChooserWidget(self, self.canvas)
+ self.layout_main.addWidget(self.colorWidget)
+
+ def showEvent(self, a0: QtGui.QShowEvent):
+ # update the colors
+ self.colorWidget.updateColorsText()
diff --git a/pylustrator/QtGuiDrag.py b/pylustrator/QtGuiDrag.py
index a16f5ee..645620d 100644
--- a/pylustrator/QtGuiDrag.py
+++ b/pylustrator/QtGuiDrag.py
@@ -20,6 +20,7 @@
# along with Pylustrator. If not, see
import sys
import traceback
+from typing import TYPE_CHECKING, Any, Optional, cast
from matplotlib import _pylab_helpers
@@ -28,13 +29,20 @@
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.axes._axes import Axes
-from matplotlib.text import Text
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets, _version_info
-if _version_info[0] == 6:
- QAction = QtGui.QAction
-else:
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
+
QAction = QtWidgets.QAction
+ from PyQt5.QtCore import pyqtSignal as Signal
+else:
+ from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets, _version_info
+
+ if _version_info[0] == 6:
+ QAction = QtGui.QAction
+ else:
+ QAction = QtWidgets.QAction
+ from qtpy.QtCore import Signal
from .ax_rasterisation import rasterizeAxes, restoreAxes
from .change_tracker import setFigureVariableNames
@@ -62,9 +70,14 @@ def my_excepthook(type, value, tback):
keys_for_lines = {}
no_save_allowed = False
+setting_use_global_variable_names = False
+old_pltshow: Optional[Any] = None
+old_pltfigure: Optional[Any] = None
-def initialize(use_global_variable_names=False, use_exception_silencer=False, disable_save=False):
+def initialize(
+ use_global_variable_names=False, use_exception_silencer=False, disable_save=False
+):
"""
This will overload the commands ``plt.figure()`` and ``plt.show()``.
If a figure is created after this command was called (directly or indirectly), a GUI window will be initialized
@@ -78,18 +91,39 @@ def initialize(use_global_variable_names=False, use_exception_silencer=False, di
use_global_variable_names : bool, optional
if used, try to find global variables that reference a figure and use them in the generated code.
"""
- global app, keys_for_lines, old_pltshow, old_pltfigure, setting_use_global_variable_names, no_save_allowed
+ global \
+ app, \
+ keys_for_lines, \
+ old_pltshow, \
+ old_pltfigure, \
+ setting_use_global_variable_names, \
+ no_save_allowed
# remember line-numbers where texts are created
def wrap_text_function(text):
def wrapped_text(*args, **kwargs):
- element = text(*args, fontdict=kwargs["fontdict"] if "fontdict" in kwargs else None)
+ element = text(
+ *args, fontdict=kwargs["fontdict"] if "fontdict" in kwargs else None
+ )
from pylustrator.change_tracker import getReference
+
stack_position = traceback.extract_stack()[-2]
- element._pylustrator_reference = dict(reference=getReference(element), stack_position=stack_position)
+ element._pylustrator_reference = dict(
+ reference=getReference(element), stack_position=stack_position
+ )
old_args = {}
- properties_to_save = ["position", "text", "ha", "va", "fontsize", "color", "style", "weight", "fontname",
- "rotation"]
+ properties_to_save = [
+ "position",
+ "text",
+ "ha",
+ "va",
+ "fontsize",
+ "color",
+ "style",
+ "weight",
+ "fontname",
+ "rotation",
+ ]
for name in properties_to_save:
try:
old_args[name] = getattr(element, f"get_{name}")()
@@ -111,17 +145,20 @@ def wrapped_text(*args, **kwargs):
Axes.text = wrap_text_function(Axes.text)
Figure.text = wrap_text_function(Figure.text)
- setattr(Figure, '_pylustrator_init', init_figure)
+ setattr(Figure, "_pylustrator_init", init_figure)
# store write only attribute
no_save_allowed = disable_save
# warning for shell session
stack_pos = traceback.extract_stack()[-2]
- if not stack_pos.filename.endswith('.py') and not stack_pos.filename.startswith(" None:
+ s_any = cast(Any, self)
+ s_any._last_saved_figure = getattr(self, "_last_saved_figure", []) + [
+ (filename, args, kwargs)
+ ]
sf(self, filename, *args, **kwargs)
- Figure.savefig = savefig
+ cast(Any, Figure).savefig = savefig
-def pyl_show(hide_window: bool = False):
- """ the function overloads the matplotlib show function.
+def pyl_show(hide_window: bool = False) -> None:
+ """the function overloads the matplotlib show function.
It opens a DragManager window instead of the default matplotlib window.
"""
global figures, app
# set an application id, so that windows properly stacks them in the task bar
- if sys.platform[:3] == 'win':
+ if sys.platform[:3] == "win":
import ctypes
- myappid = 'rgerum.pylustrator' # arbitrary string
+
+ myappid = "rgerum.pylustrator" # arbitrary string
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
+ if app is None:
+ app = QtWidgets.QApplication(sys.argv)
+ if app is None:
+ app = QtWidgets.QApplication(sys.argv)
if app is None:
app = QtWidgets.QApplication(sys.argv)
# iterate over figures
@@ -175,28 +220,33 @@ def pyl_show(hide_window: bool = False):
# window = _pylab_helpers.Gcf.figs[figure].canvas.window_pylustrator
# warn about ticks not fitting tick labels
warnAboutTicks(fig)
- # add dragger
- DragManager(fig, no_save_allowed)
+ # set up window canvas, which initializes figure._pyl_scene
window.setFigure(fig)
+ # add dragger (ChangeTracker.load() sets is_new_text on texts from generated code)
+ DragManager(fig, no_save_allowed)
+ # initialize figure defaults AFTER load, so is_new_text is properly set
+ init_figure(fig)
window.addFigure(fig)
window.update()
# and show it
if hide_window is False:
window.show()
- if hide_window is False:
+ if hide_window is False and app is not None:
# execute the application
app.exec_()
-def show(hide_window: bool = False):
- """ the function overloads the matplotlib show function.
+def show(*args, **kwargs) -> None:
+ """the function overloads the matplotlib show function.
It opens a DragManager window instead of the default matplotlib window.
"""
- global figures
+ global figures, app, old_pltshow, old_pltfigure
+ hide_window = kwargs.pop("hide_window", False)
# set an application id, so that windows properly stacks them in the task bar
- if sys.platform[:3] == 'win':
+ if sys.platform[:3] == "win":
import ctypes
- myappid = 'rgerum.pylustrator' # arbitrary string
+
+ myappid = "rgerum.pylustrator" # arbitrary string
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
# iterate over figures
for figure in _pylab_helpers.Gcf.figs.copy():
@@ -209,23 +259,26 @@ def show(hide_window: bool = False):
window.setFigure(_pylab_helpers.Gcf.figs[figure].canvas.figure)
# warn about ticks not fitting tick labels
warnAboutTicks(window.fig)
- # add dragger
+ # add dragger (ChangeTracker.load() sets is_new_text on texts from generated code)
DragManager(_pylab_helpers.Gcf.figs[figure].canvas.figure, no_save_allowed)
+ # initialize figure defaults AFTER load, so is_new_text is properly set
init_figure(_pylab_helpers.Gcf.figs[figure].canvas.figure)
window.update()
# and show it
if hide_window is False:
window.show()
- if hide_window is False:
+ if hide_window is False and app is not None:
# execute the application
app.exec_()
- plt.show = old_pltshow
- plt.figure = old_pltfigure
+ if old_pltshow is not None:
+ plt.show = old_pltshow
+ if old_pltfigure is not None:
+ plt.figure = old_pltfigure
class CmapColor(list):
- """ a color like object that has the colormap as metadata """
+ """a color like object that has the colormap as metadata"""
def setMeta(self, value, cmap):
self.value = value
@@ -233,23 +286,23 @@ def setMeta(self, value, cmap):
def patchColormapsWithMetaInfo():
- """ all colormaps now return color with metadata from which colormap the color came from """
+ """all colormaps now return color with metadata from which colormap the color came from"""
from matplotlib.colors import Colormap
cm_call = Colormap.__call__
- def new_call(self, *args, **kwargs):
+ def new_call(self, *args: Any, **kwargs: Any) -> Any:
c = cm_call(self, *args, **kwargs)
if isinstance(c, (tuple, list)):
c = CmapColor(c)
c.setMeta(args[0], self.name)
return c
- Colormap.__call__ = new_call
+ cast(Any, Colormap).__call__ = new_call
def figure(num=None, figsize=None, force_add=False, *args, **kwargs):
- """ overloads the matplotlib figure call and wraps the Figure in a PlotWindow """
+ """overloads the matplotlib figure call and wraps the Figure in a PlotWindow"""
global figures
# if num is not defined create a new number
if num is None:
@@ -257,7 +310,7 @@ def figure(num=None, figsize=None, force_add=False, *args, **kwargs):
# if number is not defined
if force_add or num not in _pylab_helpers.Gcf.figs.keys():
# create a new window and store it
- canvas = PlotWindow(num, figsize, *args, **kwargs).canvas
+ canvas = PlotWindow(num).canvas
canvas.figure.number = num
canvas.figure.clf()
canvas.manager.num = num
@@ -266,7 +319,9 @@ def figure(num=None, figsize=None, force_add=False, *args, **kwargs):
manager = _pylab_helpers.Gcf.figs[num]
# set the size if it is defined
if figsize is not None:
- _pylab_helpers.Gcf.figs[num].window.setGeometry(100, 100, figsize[0] * 80, figsize[1] * 80)
+ cast(Any, _pylab_helpers.Gcf.figs[num]).window.setGeometry(
+ 100, 100, figsize[0] * 80, figsize[1] * 80
+ )
# set the figure as the active figure
_pylab_helpers.Gcf.set_active(manager)
# return the figure
@@ -274,46 +329,54 @@ def figure(num=None, figsize=None, force_add=False, *args, **kwargs):
def warnAboutTicks(fig):
- """ warn if the tick labels and tick values do not match, to prevent users from accidentally setting wrong tick values """
+ """warn if the tick labels and tick values do not match, to prevent users from accidentally setting wrong tick values"""
import sys
+
for index, ax in enumerate(fig.axes):
ticks = ax.get_yticks()
labels = [t.get_text() for t in ax.get_yticklabels()]
- for t, l in zip(ticks, labels):
- l = l.replace("−", "-")
- if l == "":
+ for tick, label in zip(ticks, labels):
+ label = label.replace("−", "-")
+ if label == "":
continue
try:
- l = float(l)
+ label = float(label)
except ValueError:
pass
# if the label is still a string or too far away from the tick value
- if isinstance(l, str) or abs(t - l) > abs(1e-3 * t):
+ if isinstance(label, str) or abs(tick - label) > abs(1e-3 * tick):
ax_name = ax.get_label()
if ax_name == "":
ax_name = "#%d" % index
else:
ax_name = '"' + ax_name + '"'
- print("Warning tick and label differ", t, l, "for axes", ax_name, file=sys.stderr)
+ print(
+ "Warning tick and label differ",
+ tick,
+ label,
+ "for axes",
+ ax_name,
+ file=sys.stderr,
+ )
""" Window """
class Signals(QtWidgets.QWidget):
- figure_changed = QtCore.Signal(Figure)
- canvas_changed = QtCore.Signal(object)
- figure_size_changed = QtCore.Signal()
- figure_element_selected = QtCore.Signal(object)
- figure_selection_moved = QtCore.Signal()
- figure_selection_property_changed = QtCore.Signal()
- figure_selection_update = QtCore.Signal()
- figure_element_child_created = QtCore.Signal(object)
+ figure_changed = Signal(Figure)
+ canvas_changed = Signal(object)
+ figure_size_changed = Signal()
+ figure_element_selected = Signal(object)
+ figure_selection_moved = Signal()
+ figure_selection_property_changed = Signal()
+ figure_selection_update = Signal()
+ figure_element_child_created = Signal(object)
class PlotWindow(QtWidgets.QWidget):
fig = None
- update_changes_signal = QtCore.Signal(bool, bool, str, str)
+ update_changes_signal = Signal(bool, bool, str, str)
def setFigure(self, figure):
if self.fig is not None:
@@ -343,6 +406,8 @@ def undo():
# self.preview.addFigure(figure)
def selectionProperyChanged(self):
+ if self.fig is None:
+ return
self.fig.selection.update_selection_rectangles()
self.fig.selection.update_extent()
@@ -362,7 +427,7 @@ def create_menu(self, layout_parent):
file_menu.addAction(open_act)
open_act = QAction("Exit", self)
- open_act.triggered.connect(self.close)
+ open_act.triggered.connect(lambda checked=False: self.close())
open_act.setShortcut("Ctrl+Q")
file_menu.addAction(open_act)
@@ -387,13 +452,17 @@ def create_menu(self, layout_parent):
layout_parent.addWidget(self.menuBar)
def undo(self):
+ if self.fig is None:
+ return None
self.fig.figure_dragger.undo()
def redo(self):
+ if self.fig is None:
+ return None
self.fig.figure_dragger.redo()
def __init__(self, number: int = 0):
- """ The main window of pylustrator
+ """The main window of pylustrator
Args:
number: the id of the figure
@@ -405,13 +474,17 @@ def __init__(self, number: int = 0):
self.signals = Signals()
self.signals.canvas_changed.connect(self.setCanvas)
- self.signals.figure_selection_property_changed.connect(self.selectionProperyChanged)
+ self.signals.figure_selection_property_changed.connect(
+ self.selectionProperyChanged
+ )
self.plot_layout = PlotLayout(self.signals)
# widget layout and elements
self.setWindowTitle("Figure %s - Pylustrator" % number)
- self.setWindowIcon(QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icons", "logo.ico")))
+ self.setWindowIcon(
+ QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icons", "logo.ico"))
+ )
layout_parent = QtWidgets.QVBoxLayout(self)
layout_parent.setContentsMargins(0, 0, 0, 0)
@@ -439,16 +512,16 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
self.undo_act.setText(f"Undo: {undo_text}")
button_undo.setToolTip(f"Undo: {undo_text}")
else:
- self.undo_act.setText(f"Undo")
- button_undo.setToolTip(f"Undo")
+ self.undo_act.setText("Undo")
+ button_undo.setToolTip("Undo")
button_redo.setDisabled(redo)
self.redo_act.setDisabled(redo)
if redo_text != "":
self.redo_act.setText(f"Redo: {redo_text}")
button_redo.setToolTip(f"Redo: {redo_text}")
else:
- self.redo_act.setText(f"Redo")
- button_redo.setToolTip(f"Redo")
+ self.redo_act.setText("Redo")
+ button_redo.setToolTip("Redo")
self.update_changes_signal.connect(updateChangesSignal)
@@ -460,7 +533,9 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
layout_parent.addLayout(self.layout_main)
else:
self.layout_main = QtWidgets.QSplitter()
- self.layout_main.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
+ self.layout_main.setSizePolicy(
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
+ )
layout_parent.addWidget(self.layout_main)
# self.preview = FigurePreviews(self)
@@ -468,7 +543,9 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
#
widget = QtWidgets.QWidget()
self.layout_tools = QtWidgets.QVBoxLayout(widget)
- widget.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ widget.setSizePolicy(
+ QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
+ )
# widget.setMaximumWidth(350)
# widget.setMinimumWidth(350)
self.layout_main.addWidget(widget)
@@ -502,6 +579,7 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
self.layout_main.addWidget(self.plot_layout)
from .QtGui import ColorChooserWidget
+
self.colorWidget = ColorChooserWidget(self, None, self.signals)
self.colorWidget.setMaximumWidth(150)
self.layout_main.addWidget(self.colorWidget)
@@ -511,7 +589,9 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
self.layout_main.setStretchFactor(2, 0)
def rasterize(self, rasterize: bool):
- """ convert the figur elements to an image """
+ """convert the figur elements to an image"""
+ if self.fig is None:
+ return
if len(self.fig.selection.targets):
self.fig.figure_dragger.select_element(None)
if rasterize:
@@ -523,16 +603,25 @@ def rasterize(self, rasterize: bool):
self.fig.canvas.draw()
def actionSave(self):
- """ save the code for the figure """
+ """save the code for the figure"""
+ if self.fig is None:
+ return
self.fig.change_tracker.save()
- for _last_saved_figure, args, kwargs in getattr(self.fig, "_last_saved_figure", []):
+ for _last_saved_figure, args, kwargs in getattr(
+ self.fig, "_last_saved_figure", []
+ ):
self.fig.savefig(_last_saved_figure, *args, **kwargs)
def actionSaveImage(self):
- """ save figure as an image """
- path = QtWidgets.QFileDialog.getSaveFileName(self, "Save Image",
- str(getattr(self.fig, "_last_saved_figure", [(None,)])[0][0]),
- "Images (*.png *.jpg *.pdf)")
+ """save figure as an image"""
+ if self.fig is None:
+ return
+ path = QtWidgets.QFileDialog.getSaveFileName(
+ self,
+ "Save Image",
+ str(getattr(self.fig, "_last_saved_figure", [(None,)])[0][0]),
+ "Images (*.png *.jpg *.pdf)",
+ )
if isinstance(path, tuple):
path = str(path[0])
else:
@@ -546,21 +635,25 @@ def actionSaveImage(self):
print("Saved plot image as", path)
def showInfo(self):
- """ show the info dialog """
+ """show the info dialog"""
self.info_dialog = InfoDialog(self)
self.info_dialog.show()
def showEvent(self, event: QtCore.QEvent):
- """ when the window is shown """
+ """when the window is shown"""
self.colorWidget.updateColorsText()
def update(self):
- """ update the tree view """
+ """update the tree view"""
+ if self.fig is None:
+ return
# self.input_size.setValue(np.array(self.fig.get_size_inches()) * 2.54)
def wrap(func):
def newfunc(element, event=None):
+ if self.fig is None:
+ return
self.fig.no_figure_dragger_selection_update = True
self.signals.figure_element_selected.emit(element)
ret = func(element, event)
@@ -585,19 +678,29 @@ def newfunc(*args):
self.signals.figure_element_selected.emit(self.fig)
def updateTitle(self):
- """ update the title of the window to display if it is saved or not """
+ """update the title of the window to display if it is saved or not"""
+ if self.fig is None:
+ return
if self.fig.change_tracker.saved:
self.setWindowTitle("Figure %s - Pylustrator" % self.fig.number)
else:
self.setWindowTitle("Figure %s* - Pylustrator" % self.fig.number)
def closeEvent(self, event: QtCore.QEvent):
- """ when the window is closed, ask the user to save """
+ """when the window is closed, ask the user to save"""
+ if self.fig is None:
+ return
if not self.fig.change_tracker.saved and not no_save_allowed:
- reply = QtWidgets.QMessageBox.question(self, 'Warning - Pylustrator', 'The figure has not been saved. '
- 'All data will be lost.\nDo you want to save it?',
- QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes,
- QtWidgets.QMessageBox.Yes)
+ reply = QtWidgets.QMessageBox.question(
+ self,
+ "Warning - Pylustrator",
+ "The figure has not been saved. "
+ "All data will be lost.\nDo you want to save it?",
+ QtWidgets.QMessageBox.Cancel
+ | QtWidgets.QMessageBox.No
+ | QtWidgets.QMessageBox.Yes,
+ QtWidgets.QMessageBox.Yes,
+ )
if reply == QtWidgets.QMessageBox.Cancel:
event.ignore()
diff --git a/pylustrator/QtShortCuts.py b/pylustrator/QtShortCuts.py
index 74d1d53..0d96207 100644
--- a/pylustrator/QtShortCuts.py
+++ b/pylustrator/QtShortCuts.py
@@ -19,37 +19,46 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
+ from PyQt5.QtCore import pyqtSignal as Signal
+else:
+ from qtpy import QtCore, QtGui, QtWidgets
+ from qtpy.QtCore import Signal
from matplotlib import pyplot as plt
import matplotlib as mpl
""" Color Chooser """
+
class QDragableColor(QtWidgets.QLabel):
- """ a color widget that can be dragged onto another QDragableColor widget to exchange the two colors.
+ """a color widget that can be dragged onto another QDragableColor widget to exchange the two colors.
Alternatively it can be right-clicked to select either a color or a colormap through their respective menus.
The button can represent either a single color or a colormap.
"""
- color_changed = QtCore.Signal(str)
- color_changed_by_color_picker = QtCore.Signal(bool)
+ color_changed = Signal(str)
+ color_changed_by_color_picker = Signal(bool)
def __init__(self, value: str):
- """ initialize with a color """
+ """initialize with a color"""
super().__init__(value)
import matplotlib.pyplot as plt
+
self.maps = plt.colormaps()
self.setAcceptDrops(True)
- self.setAlignment(QtCore.Qt.AlignHCenter)
+ self.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)
self.setColor(value, True)
def getBackground(self) -> str:
- """ get the background of the color button """
+ """get the background of the color button"""
try:
cmap = plt.get_cmap(self.color)
- except:
+ except ValueError:
return ""
text = "background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, "
N = 10
@@ -60,78 +69,100 @@ def getBackground(self) -> str:
return text
def setColor(self, value: str, no_signal=False):
- """ set the current color """
+ """set the current color"""
# display and save the new color
self.color = value
self.setText(value)
self.color_changed.emit(value)
if value in self.maps:
- self.setStyleSheet("text-align: center; border: 2px solid black; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet(
+ "text-align: center; border: 2px solid black; padding: 0.1em; "
+ + self.getBackground()
+ )
else:
- self.setStyleSheet(f"text-align: center; background-color: {value}; border: 2px solid black; padding: 0.1em; ")
+ self.setStyleSheet(
+ f"text-align: center; background-color: {value}; border: 2px solid black; padding: 0.1em; "
+ )
def getColor(self) -> str:
- """ get the current color """
+ """get the current color"""
# return the color
return self.color
def mousePressEvent(self, event):
- """ when a mouse button is pressed """
+ """when a mouse button is pressed"""
# a left mouse button lets the user drag the color
- if event.button() == QtCore.Qt.LeftButton:
+ if event.button() == QtCore.Qt.MouseButton.LeftButton:
drag = QtGui.QDrag(self)
drag.setPixmap(self.grab())
mime = QtCore.QMimeData()
mime.setText(self.color)
drag.setMimeData(mime)
- self.setStyleSheet("background-color: lightgray; border: 2px solid gray; padding: 0.1em; ")
+ self.setStyleSheet(
+ "background-color: lightgray; border: 2px solid gray; padding: 0.1em; "
+ )
self.setDisabled(True)
self.setText("")
drag.exec()
self.setText(self.color)
self.setDisabled(False)
if self.color in self.maps:
- self.setStyleSheet("text-align: center; border: 2px solid black; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet(
+ "text-align: center; border: 2px solid black; padding: 0.1em; "
+ + self.getBackground()
+ )
else:
- self.setStyleSheet(f"text-align: center; background-color: {self.color}; border: 2px solid black; padding: 0.1em; ")
+ self.setStyleSheet(
+ f"text-align: center; background-color: {self.color}; border: 2px solid black; padding: 0.1em; "
+ )
# a right mouse button opens a color choose menu
- elif event.button() == QtCore.Qt.RightButton:
+ elif event.button() == QtCore.Qt.MouseButton.RightButton:
self.openDialog()
def dragEnterEvent(self, event):
- """ when a color widget is dragged over the current widget """
+ """when a color widget is dragged over the current widget"""
if event.mimeData().hasFormat("text/plain") and event.source() != self:
event.acceptProposedAction()
if self.color in self.maps:
- self.setStyleSheet("border: 2px solid red; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet(
+ "border: 2px solid red; padding: 0.1em; " + self.getBackground()
+ )
else:
- self.setStyleSheet(f"background-color: {self.color}; border: 2px solid red; padding: 0.1em; ")
+ self.setStyleSheet(
+ f"background-color: {self.color}; border: 2px solid red; padding: 0.1em; "
+ )
def dragLeaveEvent(self, event):
- """ when the color widget which is dragged leaves the area of this widget """
+ """when the color widget which is dragged leaves the area of this widget"""
if self.color in self.maps:
- self.setStyleSheet("border: 2px solid black; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet(
+ "border: 2px solid black; padding: 0.1em; " + self.getBackground()
+ )
else:
- self.setStyleSheet(f"background-color: {self.color}; border: 2px solid black; padding: 0.1em; ")
+ self.setStyleSheet(
+ f"background-color: {self.color}; border: 2px solid black; padding: 0.1em; "
+ )
def dropEvent(self, event):
- """ when a color widget is dropped here, exchange the two colors """
+ """when a color widget is dropped here, exchange the two colors"""
color = event.source().getColor()
event.source().setColor(self.getColor())
self.setColor(color)
def openDialog(self):
- """ open a color choosed dialog """
+ """open a color choosed dialog"""
if self.color in self.maps:
- dialog = ColorMapChoose(self.parent(), self.color)
+ dialog = ColorMapChoose(self.parent(), self.color) # ty:ignore[invalid-argument-type]
colormap, selected = dialog.exec()
if selected is False:
return
self.setColor(colormap)
else:
# get new color from color picker
- qcolor = QtGui.QColor(*tuple(int(x * 255) for x in mpl.colors.to_rgb(self.getColor())))
- color = QtWidgets.QColorDialog.getColor(qcolor, self.parent())
+ qcolor = QtGui.QColor(
+ *tuple(int(x * 255) for x in mpl.colors.to_rgb(self.getColor()))
+ )
+ color = QtWidgets.QColorDialog.getColor(qcolor, self.parent()) # ty:ignore[invalid-argument-type]
# if a color is set, apply it
if color.isValid():
color = "#%02x%02x%02x" % color.getRgb()[:3]
@@ -139,17 +170,17 @@ def openDialog(self):
self.color_changed_by_color_picker.emit(True)
-
class ColorMapChoose(QtWidgets.QDialog):
- """ A dialog to select a colormap """
+ """A dialog to select a colormap"""
+
result = ""
def __init__(self, parent: QtWidgets.QWidget, map):
- """ initialize the dialog with all the colormap of matplotlib """
+ """initialize the dialog with all the colormap of matplotlib"""
QtWidgets.QDialog.__init__(self, parent)
main_layout = QtWidgets.QVBoxLayout(self)
- self.layout = QtWidgets.QHBoxLayout()
- main_layout.addLayout(self.layout)
+ self.layout_main = QtWidgets.QHBoxLayout()
+ main_layout.addLayout(self.layout_main)
button_layout = QtWidgets.QHBoxLayout()
main_layout.addLayout(button_layout)
self.button_cancel = QtWidgets.QPushButton("Cancel")
@@ -163,27 +194,112 @@ def __init__(self, parent: QtWidgets.QWidget, map):
# Have colormaps separated into categories:
# http://matplotlib.org/examples/color/colormaps_reference.html
- cmaps = [('Perceptually Uniform Sequential', [
- 'viridis', 'plasma', 'inferno', 'magma']),
- ('Sequential', [
- 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
- 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
- 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']),
- ('Sequential (2)', [
- 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
- 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
- 'hot', 'afmhot', 'gist_heat', 'copper']),
- ('Diverging', [
- 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
- 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']),
- ('Qualitative', [
- 'Pastel1', 'Pastel2', 'Paired', 'Accent',
- 'Dark2', 'Set1', 'Set2', 'Set3',
- 'tab10', 'tab20', 'tab20b', 'tab20c']),
- ('Miscellaneous', [
- 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
- 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'hsv',
- 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])]
+ cmaps = [
+ (
+ "Perceptually Uniform Sequential",
+ ["viridis", "plasma", "inferno", "magma"],
+ ),
+ (
+ "Sequential",
+ [
+ "Greys",
+ "Purples",
+ "Blues",
+ "Greens",
+ "Oranges",
+ "Reds",
+ "YlOrBr",
+ "YlOrRd",
+ "OrRd",
+ "PuRd",
+ "RdPu",
+ "BuPu",
+ "GnBu",
+ "PuBu",
+ "YlGnBu",
+ "PuBuGn",
+ "BuGn",
+ "YlGn",
+ ],
+ ),
+ (
+ "Sequential (2)",
+ [
+ "binary",
+ "gist_yarg",
+ "gist_gray",
+ "gray",
+ "bone",
+ "pink",
+ "spring",
+ "summer",
+ "autumn",
+ "winter",
+ "cool",
+ "Wistia",
+ "hot",
+ "afmhot",
+ "gist_heat",
+ "copper",
+ ],
+ ),
+ (
+ "Diverging",
+ [
+ "PiYG",
+ "PRGn",
+ "BrBG",
+ "PuOr",
+ "RdGy",
+ "RdBu",
+ "RdYlBu",
+ "RdYlGn",
+ "Spectral",
+ "coolwarm",
+ "bwr",
+ "seismic",
+ ],
+ ),
+ (
+ "Qualitative",
+ [
+ "Pastel1",
+ "Pastel2",
+ "Paired",
+ "Accent",
+ "Dark2",
+ "Set1",
+ "Set2",
+ "Set3",
+ "tab10",
+ "tab20",
+ "tab20b",
+ "tab20c",
+ ],
+ ),
+ (
+ "Miscellaneous",
+ [
+ "flag",
+ "prism",
+ "ocean",
+ "gist_earth",
+ "terrain",
+ "gist_stern",
+ "gnuplot",
+ "gnuplot2",
+ "CMRmap",
+ "cubehelix",
+ "brg",
+ "hsv",
+ "gist_rainbow",
+ "rainbow",
+ "jet",
+ "nipy_spectral",
+ "gist_ncar",
+ ],
+ ),
+ ]
for cmap_category, cmap_list in cmaps:
layout = QtWidgets.QVBoxLayout()
@@ -192,30 +308,34 @@ def __init__(self, parent: QtWidgets.QWidget, map):
label.setFixedWidth(150)
for cmap in cmap_list:
button = QtWidgets.QPushButton(cmap)
- button.setStyleSheet("text-align: center; border: 2px solid black; "+self.getBackground(cmap))
+ button.setStyleSheet(
+ "text-align: center; border: 2px solid black; "
+ + self.getBackground(cmap)
+ )
button.clicked.connect(lambda _, cmap=cmap: self.buttonClicked(cmap))
self.buttons.append(button)
layout.addWidget(button)
layout.addStretch()
- self.layout.addLayout(layout)
+ self.layout_main.addLayout(layout)
def buttonClicked(self, text: str):
- """ the used as selected a colormap, we are done """
+ """the used as selected a colormap, we are done"""
self.result = text
self.done(1)
def exec(self):
- """ execute the dialog and return the result """
+ """execute the dialog and return the result"""
result = QtWidgets.QDialog.exec(self)
return self.result, result == 1
def getBackground(self, color: str) -> str:
- """ convert a colormap to a gradient background """
+ """convert a colormap to a gradient background"""
import matplotlib.pyplot as plt
import matplotlib as mpl
+
try:
cmap = plt.get_cmap(color)
- except:
+ except ValueError:
return ""
text = "background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, "
N = 10
diff --git a/pylustrator/__init__.py b/pylustrator/__init__.py
index 207478d..4ba40a7 100644
--- a/pylustrator/__init__.py
+++ b/pylustrator/__init__.py
@@ -20,9 +20,45 @@
# along with Pylustrator. If not, see
from .QtGuiDrag import initialize as start
-from .helper_functions import fig_text, add_axes, add_image, despine, changeFigureSize, mark_inset, VoronoiPlot, selectRectangle, mark_inset_pos, draw_from_point_to_bbox, draw_from_point_to_point, loadFigureFromFile, add_letter, add_letters
+from .helper_functions import (
+ fig_text,
+ add_axes,
+ add_image,
+ despine,
+ changeFigureSize,
+ mark_inset,
+ VoronoiPlot,
+ selectRectangle,
+ mark_inset_pos,
+ draw_from_point_to_bbox,
+ draw_from_point_to_point,
+ loadFigureFromFile,
+ add_letter,
+ add_letters,
+)
from .QtGui import initialize as StartColorChooser
from .lab_colormap import LabColormap
from .helper_functions import loadFigureFromFile as load
-__version__ = '1.3.0'
+__version__ = "1.3.0"
+
+__all__ = [
+ "start",
+ "fig_text",
+ "add_axes",
+ "add_image",
+ "despine",
+ "changeFigureSize",
+ "mark_inset",
+ "VoronoiPlot",
+ "selectRectangle",
+ "mark_inset_pos",
+ "draw_from_point_to_bbox",
+ "draw_from_point_to_point",
+ "loadFigureFromFile",
+ "add_letter",
+ "add_letters",
+ "StartColorChooser",
+ "LabColormap",
+ "load",
+]
diff --git a/pylustrator/_types.py b/pylustrator/_types.py
new file mode 100644
index 0000000..0b648ef
--- /dev/null
+++ b/pylustrator/_types.py
@@ -0,0 +1,38 @@
+"""Type definitions for pylustrator."""
+
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, runtime_checkable
+
+if TYPE_CHECKING:
+ from matplotlib.axes import Axes
+ from matplotlib.backend_bases import FigureCanvasBase
+ from .change_tracker import ChangeTracker
+ from .drag_helper import DragManager
+
+
+@runtime_checkable
+class PylustatorFigure(Protocol):
+ """Protocol for Figure objects with pylustrator extensions."""
+
+ # Standard matplotlib Figure attributes
+ axes: List["Axes"]
+ canvas: "FigureCanvasBase"
+
+ # Pylustrator-specific attributes
+ change_tracker: "ChangeTracker"
+ figure_dragger: "DragManager"
+ selection: Any # GrabbableRectangleSelection
+ signals: Any # Signals object
+ window: Any # PlotWindow
+ ax_dict: Dict[str, "Axes"]
+ _pyl_scene: Any
+ _pyl_graphics_scene_snapparent: Any
+ color_artists: List[Any]
+ _variable_name: Optional[str]
+ _last_saved_figure: List[tuple]
+ no_figure_dragger_selection_update: bool
+
+ def get_size_inches(self) -> tuple: ...
+ def set_size_inches(
+ self, w: float, h: float = ..., forward: bool = ...
+ ) -> None: ...
+ def savefig(self, fname: Any, **kwargs: Any) -> None: ...
diff --git a/pylustrator/arc2bez.py b/pylustrator/arc2bez.py
index bb7ae37..2603982 100644
--- a/pylustrator/arc2bez.py
+++ b/pylustrator/arc2bez.py
@@ -21,6 +21,7 @@
import numpy as np
+
def mapToEllipse(pos, rx, ry, cosphi, sinphi, centerx, centery):
x, y = pos
x *= rx
@@ -40,11 +41,7 @@ def approxUnitArc(ang1, ang2):
x2 = np.cos(ang1 + ang2)
y2 = np.sin(ang1 + ang2)
- return [
- [x1 - y1 * a, y1 + x1 * a],
- [x2 + y2 * a, y2 - x2 * a],
- [x2, y2]
- ]
+ return [[x1 - y1 * a, y1 + x1 * a], [x2 + y2 * a, y2 - x2 * a], [x2, y2]]
def vectorAngle(ux, uy, vx, vy):
@@ -54,15 +51,16 @@ def vectorAngle(ux, uy, vx, vy):
if dot > 1:
dot = 1
-
+
if dot < -1:
dot = -1
return sign * np.arccos(dot)
-def getArcCenter (px, py, cx, cy, rx, ry,
- largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp):
+def getArcCenter(
+ px, py, cx, cy, rx, ry, largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp
+):
rxsq = np.power(rx, 2)
rysq = np.power(ry, 2)
pxpsq = np.power(pxp, 2)
@@ -91,15 +89,15 @@ def getArcCenter (px, py, cx, cy, rx, ry,
ang2 = vectorAngle(vx1, vy1, vx2, vy2)
if sweepFlag == 0 and ang2 > 0:
- ang2 -= np.pi*2
+ ang2 -= np.pi * 2
if sweepFlag == 1 and ang2 < 0:
- ang2 += np.pi*2
+ ang2 += np.pi * 2
return centerx, centery, ang1, ang2
-def arcToBezier(pos1, pos2, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFlag = 0):
+def arcToBezier(pos1, pos2, rx, ry, xAxisRotation=0, largeArcFlag=0, sweepFlag=0):
px, py = pos1
cx, cy = pos2
curves = []
@@ -125,8 +123,9 @@ def arcToBezier(pos1, pos2, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFl
rx *= np.sqrt(lambda_)
ry *= np.sqrt(lambda_)
- centerx, centery, ang1, ang2 = getArcCenter(px, py, cx, cy, rx, ry,
- largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp)
+ centerx, centery, ang1, ang2 = getArcCenter(
+ px, py, cx, cy, rx, ry, largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp
+ )
# If 'ang2' == 90.0000000001, then `ratio` will evaluate to
# 1.0000000001. This causes `segments` to be greater than one, which is an
diff --git a/pylustrator/ax_rasterisation.py b/pylustrator/ax_rasterisation.py
index 399335a..7748c18 100644
--- a/pylustrator/ax_rasterisation.py
+++ b/pylustrator/ax_rasterisation.py
@@ -21,10 +21,11 @@
import io
import matplotlib.pyplot as plt
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
- from matplotlib.axes._subplots import Axes
+except ImportError:
+ from matplotlib.axes._subplots import Axes # ty:ignore[unresolved-import]
from matplotlib.figure import Figure
from typing import List
@@ -32,7 +33,7 @@
def stashElements(ax: Axes, names: List[str]):
- """ remove elements from a figure and store them"""
+ """remove elements from a figure and store them"""
for attribute in names:
element = getattr(ax, attribute)
setattr(ax, "pylustrator_" + attribute, element)
@@ -40,7 +41,7 @@ def stashElements(ax: Axes, names: List[str]):
def popStashedElements(ax: Axes, names: List[str]):
- """ add elements to a figure that were previously removed from it """
+ """add elements to a figure that were previously removed from it"""
for attribute in names:
element_list = getattr(ax, attribute)
if isinstance(element_list, list):
@@ -53,7 +54,7 @@ def popStashedElements(ax: Axes, names: List[str]):
def rasterizeAxes(fig: Figure):
- """ replace contents of a figure with a rasterized image of it """
+ """replace contents of a figure with a rasterized image of it"""
restoreAxes(fig)
parts = removeContentFromFigure(fig)
@@ -66,7 +67,7 @@ def rasterizeAxes(fig: Figure):
addContentToFigure(fig, [ax])
buf = io.BytesIO()
- fig.savefig(buf, format='png', dpi=100)
+ fig.savefig(buf, format="png", dpi=100)
buf.seek(0)
im = plt.imread(buf)
buf.close()
@@ -74,23 +75,31 @@ def rasterizeAxes(fig: Figure):
bbox = ax.get_position()
sx = im.shape[1]
sy = im.shape[0]
- x1, x2 = int(bbox.x0*sx+1), int(bbox.x1*sx-1)
- y2, y1 = sy-int(bbox.y0*sy+1), sy-int(bbox.y1*sy-1)
+ x1, x2 = int(bbox.x0 * sx + 1), int(bbox.x1 * sx - 1)
+ y2, y1 = sy - int(bbox.y0 * sy + 1), sy - int(bbox.y1 * sy - 1)
im2 = im[y1:y2, x1:x2]
stashElements(ax, ["lines", "images", "patches"])
sx2 = ax.get_xlim()[1] - ax.get_xlim()[0]
sy2 = ax.get_ylim()[1] - ax.get_ylim()[0]
- x1_offset = 1/sx/bbox.width*sx2
- x2_offset = 1/sx/bbox.width*sx2
+ x1_offset = 1 / sx / bbox.width * sx2
+ x2_offset = 1 / sx / bbox.width * sx2
y1_offset = 1 / sy / bbox.height * sy2
y2_offset = 1 / sy / bbox.height * sy2
xlim = ax.get_xlim()
ylim = ax.get_ylim()
- ax.pylustrator_rasterized = ax.imshow(im2, extent=[ax.get_xlim()[0]+x1_offset, ax.get_xlim()[1]-x2_offset-x1_offset,
- ax.get_ylim()[0]+y1_offset, ax.get_ylim()[1]-y2_offset-y1_offset], aspect="auto")
+ ax.pylustrator_rasterized = ax.imshow(
+ im2,
+ extent=[
+ ax.get_xlim()[0] + x1_offset,
+ ax.get_xlim()[1] - x2_offset - x1_offset,
+ ax.get_ylim()[0] + y1_offset,
+ ax.get_ylim()[1] - y2_offset - y1_offset,
+ ],
+ aspect="auto",
+ )
ax.set_xlim(xlim)
ax.set_ylim(ylim)
@@ -100,7 +109,7 @@ def rasterizeAxes(fig: Figure):
def restoreAxes(fig: Figure):
- """ restore contents of a figure """
+ """restore contents of a figure"""
list_axes = fig.axes
for ax in list_axes:
im = getattr(ax, "pylustrator_rasterized", None)
diff --git a/pylustrator/change_tracker.py b/pylustrator/change_tracker.py
index 57260a0..65cd3aa 100644
--- a/pylustrator/change_tracker.py
+++ b/pylustrator/change_tracker.py
@@ -1,1029 +1,1316 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# change_tracker.py
-
-# Copyright (c) 2016-2020, Richard Gerum
-#
-# This file is part of Pylustrator.
-#
-# Pylustrator is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pylustrator is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pylustrator. If not, see
-
-import re
-import sys
-import traceback
-from typing import IO
-from packaging import version
-
-import numpy as np
-import matplotlib
-import matplotlib as mpl
-import matplotlib.pyplot as plt
-from matplotlib import _pylab_helpers
-from matplotlib.artist import Artist
-try: # starting from mpl version 3.6.0
- from matplotlib.axes import Axes
-except:
- from matplotlib.axes._subplots import Axes
-from matplotlib.collections import Collection
-from matplotlib.figure import Figure
-try:
- from matplotlib.figure import SubFigure # since matplotlib 3.4.0
-except ImportError:
- SubFigure = None
-from matplotlib.lines import Line2D
-from matplotlib.patches import Rectangle
-from matplotlib.text import Text
-from matplotlib.legend import Legend
-try:
- from natsort import natsorted
-except:
- natsorted = sorted
-
-from .exception_swallower import Dummy
-from .jupyter_cells import open
-from .helper_functions import main_figure
-
-
-""" External overload """
-class CustomStackPosition:
- filename = None
- lineno = None
- def __init__(self, filename, lineno):
- self.filename = filename
- self.lineno = lineno
-custom_stack_position = None
-custom_prepend = ""
-custom_append = ""
-
-escape_pairs = [
- ("\\", "\\\\"),
- ("\n", "\\n"),
- ("\r", "\\r"),
- ("\"", "\\\""),
-]
-def escape_string(str):
- for pair in escape_pairs:
- str = str.replace(pair[0], pair[1])
- return str
-
-def unescape_string(str):
- for pair in escape_pairs:
- str = str.replace(pair[1], pair[0])
- return str
-
-def to_str(v):
- if isinstance(v, list) and len(v) and isinstance(v[0], float):
- return "["+", ".join(np.format_float_positional(a, 4, fractional=False, trim=".") for a in v)+"]"
- elif isinstance(v, tuple) and len(v) and isinstance(v[0], float):
- return "("+", ".join(np.format_float_positional(a, 4, fractional=False, trim=".") for a in v)+")"
- elif isinstance(v, float):
- return np.format_float_positional(v, 4, fractional=False, trim=".")
- return repr(v)
-def kwargs_to_string(kwargs):
- return ', '.join(f'{k}={to_str(v)}' for k, v in kwargs.items())
-
-class UndoRedo:
- def __init__(self, elements, name):
- self.elements = list(elements)
- self.name = name
- if len(elements):
- self.figure = main_figure(elements[0])
- self.change_tracker = self.figure.change_tracker
-
- def __enter__(self):
- if len(self.elements):
- self.undo = self.change_tracker.get_element_restore_function(self.elements)
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- if len(self.elements):
- self.redo = self.change_tracker.get_element_restore_function(self.elements)
- self.redo()
- self.figure.canvas.draw()
- self.figure.signals.figure_selection_property_changed.emit()
- self.change_tracker.addEdit([self.undo, self.redo, self.name])
-
-def init_figure(fig):
- for axes in fig.axes:
- add_axes_default(axes)
- add_text_default(axes.title)
- add_text_default(axes._left_title)
- add_text_default(axes._right_title)
- for text in axes.texts:
- add_text_default(text)
- for text in fig.texts:
- add_text_default(text)
- for subfig in fig.subfigs:
- for text in subfig.texts:
- add_text_default(text)
-
-def add_text_default(element):
- # properties to store
- properties = ["position", "text", "ha", "va", "fontsize", "color", "style", "weight", "fontname", "rotation"]
-
- # if the default properties are not set, aquire them
- if getattr(element, "_pylustrator_old_args", None) is None:
- old_args = {}
- for name in properties:
- try:
- old_args[name] = getattr(element, f"get_{name}")()
- except AttributeError:
- continue
- if getattr(element, "is_new_text", False):
- old_args["position"] = None
- old_args["text"] = None
- element._pylustrator_old_args = old_args
-
-def add_axes_default(element):
- properties = ["position",
- "xlim", "xlabel", "xticks", "xticklabels", "xscale",
- "ylim", "ylabel", "yticks", "yticklabels", "yscale",
- "zorder"
- ]
- if getattr(element, "_pylustrator_old_args", None) is None:
- old_args = {}
- for name in properties:
- try:
- old_args[name] = getattr(element, f"get_{name}")()
- except AttributeError:
- continue
- pos = element.get_position()
- old_args["position"] = [pos.x0, pos.y0, pos.width, pos.height]
- old_args["xticks"] = list(old_args["xticks"])
- old_args["xticks-locator"] = element.get_xaxis().major.locator
- old_args["xticklabels"] = [t.get_text() for t in old_args["xticklabels"]]
- old_args["yticks"] = list(old_args["yticks"])
- old_args["yticks-locator"] = element.get_yaxis().major.locator
- old_args["yticklabels"] = [t.get_text() for t in old_args["yticklabels"]]
- old_args["grid"] = getattr(element.xaxis, "_gridOnMajor", False) or getattr(element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']
- old_args["spines"] = {s: v.get_visible() for s,v in element.spines.items()}
-
- old_args["xticks-minor"] = list(element.get_xticks(minor=True))
- old_args["xticklabels-minor"] = [t.get_text() for t in element.get_xticklabels(minor=True)]
- old_args["yticks-minor"] = list(element.get_yticks(minor=True))
- old_args["yticklabels-minor"] = [t.get_text() for t in element.get_yticklabels(minor=True)]
- element._pylustrator_old_args = old_args
- add_text_default(element.get_xaxis().get_label())
- add_text_default(element.get_yaxis().get_label())
-
-def getReference(element: Artist, allow_using_variable_names=True):
- """ get the code string that represents the given Artist. """
- if element is None:
- return ""
- if isinstance(element, Figure):
- if allow_using_variable_names:
- name = getattr(element, "_variable_name", None)
- if name is not None:
- return name
- if isinstance(element.number, (float, int)):
- return "plt.figure(%s)" % element.number
- else:
- return "plt.figure(\"%s\")" % element.number
- # subfigures are only available in matplotlib>=3.4.0
- if version.parse(mpl.__version__) >= version.parse("3.4.0") and isinstance(element, SubFigure):
- index = element._parent.subfigs.index(element)
- return getReference(element._parent) + ".subfigs[%d]" % index
- if isinstance(element, matplotlib.lines.Line2D):
- index = element.axes.lines.index(element)
- return getReference(element.axes) + ".lines[%d]" % index
- if isinstance(element, matplotlib.collections.Collection):
- index = element.axes.collections.index(element)
- return getReference(element.axes) + ".collections[%d]" % index
- if isinstance(element, matplotlib.patches.Patch):
- if element.axes:
- index = element.axes.patches.index(element)
- return getReference(element.axes) + ".patches[%d]" % index
- index = element.figure.patches.index(element)
- return getReference(element.figure) + ".patches[%d]" % (index)
-
- if isinstance(element, matplotlib.text.Text):
- if element.axes:
- try:
- index = element.axes.texts.index(element)
- except ValueError:
- for attribute_name in ["title", "_left_title", "_right_title"]:
- if getattr(element.axes, attribute_name, None) == element:
- return getReference(element.axes) + "." + attribute_name
- pass
- else:
- return getReference(element.axes) + ".texts[%d]" % index
- try:
- index = element.figure.texts.index(element)
- return getReference(element.figure) + ".texts[%d]" % (index)
- except ValueError:
- pass
- for axes in element.figure.axes:
- if element == axes.get_xaxis().get_label():
- return getReference(axes) + ".get_xaxis().get_label()"
- if element == axes.get_yaxis().get_label():
- return getReference(axes) + ".get_yaxis().get_label()"
-
- for index, label in enumerate(axes.get_xaxis().get_major_ticks()):
- if element == label.label1:
- return getReference(axes) + ".get_xaxis().get_major_ticks()[%d].label1" % index
- if element == label.label2:
- return getReference(axes) + ".get_xaxis().get_major_ticks()[%d].label2" % index
- for index, label in enumerate(axes.get_xaxis().get_minor_ticks()):
- if element == label.label1:
- return getReference(axes) + ".get_xaxis().get_minor_ticks()[%d].label1" % index
- if element == label.label2:
- return getReference(axes) + ".get_xaxis().get_minor_ticks()[%d].label2" % index
-
- for axes in element.figure.axes:
- for index, label in enumerate(axes.get_yaxis().get_major_ticks()):
- if element == label.label1:
- return getReference(axes) + ".get_yaxis().get_major_ticks()[%d].label1" % index
- if element == label.label2:
- return getReference(axes) + ".get_yaxis().get_major_ticks()[%d].label2" % index
- for index, label in enumerate(axes.get_yaxis().get_minor_ticks()):
- if element == label.label1:
- return getReference(axes) + ".get_yaxis().get_minor_ticks()[%d].label1" % index
- if element == label.label2:
- return getReference(axes) + ".get_yaxis().get_minor_ticks()[%d].label2" % index
-
- if isinstance(element, matplotlib.axes._axes.Axes):
- if element.get_label():
- def check_fig_has_label(fig):
- for ax in element.figure.axes:
- if ax != element and ax.get_label() == element.get_label():
- return True
- return False
- if not check_fig_has_label(element.figure):
- return getReference(element.figure) + ".ax_dict[\"%s\"]" % escape_string(element.get_label())
- index = element.figure.axes.index(element)
- return getReference(element.figure) + ".axes[%d]" % index
-
- if isinstance(element, matplotlib.legend.Legend):
- return getReference(element.axes) + ".get_legend()"
- raise TypeError(str(type(element)) + " not found")
-
-
-def setFigureVariableNames(figure: Figure):
- """ get the global variable names that refer to the given figure """
- import inspect
- mpl_figure = _pylab_helpers.Gcf.figs[figure].canvas.figure
- calling_globals = inspect.stack()[2][0].f_globals
- fig_names = [
- name
- for name, val in calling_globals.items()
- if isinstance(val, mpl.figure.Figure) and hash(val) == hash(mpl_figure)
- ]
- #print("fig_names", fig_names)
- if len(fig_names):
- globals()[fig_names[0]] = mpl_figure
- setattr(mpl_figure, "_variable_name", fig_names[0])
-
-
-class ChangeTracker:
- """ a class that records a list of the change to the figure """
- changes = None
- saved = True
-
- update_changes_signal = None
-
- def __init__(self, figure: Figure, no_save):
- global stack_position
- self.figure = figure
- self.edits = []
- self.last_edit = -1
- self.changes = {}
- self.no_save = no_save
-
- # make all the subplots pickable
- for index, axes in enumerate(self.figure.axes):
- # axes.set_title(index)
- axes.number = index
-
- # store the position where StartPylustrator was called
- if custom_stack_position is None:
- stack_position = traceback.extract_stack()[-4]
- else:
- stack_position = custom_stack_position
-
- self.fig_inch_size = self.figure.get_size_inches()
-
- self.load()
-
- def addChange(self, command_obj: Artist, command: str, reference_obj: Artist = None, reference_command: str = None):
- """ add a change """
- command = command.replace("\n", "\\n")
- if reference_obj is None:
- reference_obj = command_obj
- if reference_command is None:
- reference_command, = re.match(r"(\.[^(=]*)", command).groups()
- self.changes[reference_obj, reference_command] = (command_obj, command)
- self.saved = False
- self.changeCountChanged()
-
- def get_element_restore_function(self, elements):
- description_strings = []
- for element in elements:
- desc = self.get_describtion_string(element, exclude_default=False)
- if isinstance(desc, list):
- description_strings.extend(desc)
- else:
- description_strings.append(desc)
- def restore():
- for element, string in description_strings:
- #function, arguments = re.match(r"\.([^(]*)\((.*)\)", string)
- print(f"eval {getReference(element)}{string}")
- eval(f"{getReference(element)}{string}")
- if isinstance(element, Text):
- self.addNewTextChange(element)
- elif isinstance(element, Axes) and string.startswith(".legend("):
- self.addNewLegendChange(element.get_legend())
- elif isinstance(element, Axes):
- self.addNewAxesChange(element)
- else:
- raise NotImplementedError
- #getattr(element, function)(eval(arg))
- return restore
-
- def get_describtion_string(self, element, exclude_default=True):
- if isinstance(element, Text):
- # if the text is deleted we do not need to store all properties
- if not element.get_visible() or element.get_text() == "":
- if getattr(element, "is_new_text", False):
- return element.axes or element.figure, f".text(0, 0, "", visible=False)"
- else:
- is_label = np.any([ax.xaxis.get_label() == element or ax.yaxis.get_label() == element for ax in
- element.figure.axes])
- if is_label:
- return element, f".set(text='')"
- return element, f".set(visible=False)"
-
- # properties to store
- properties = ["position", "text", "ha", "va", "fontsize", "color", "style", "weight", "fontname", "rotation"]
-
- # get current property values
- kwargs = {}
- for prop in properties:
- value = getattr(element, f"get_{prop}")()
- default = element._pylustrator_old_args[prop]
- if to_str(default) != to_str(value) or not exclude_default:
- kwargs[prop] = value
-
- # get position and transformation
- pos = element.get_position()
- if element.axes:
- transform = getReference(element.axes) + '.transAxes'
- else:
- transform = getReference(element.figure) + '.transFigure'
-
- # compose text
- if getattr(element, "is_new_text", False) and exclude_default:
- text = kwargs["text"]
- del kwargs["text"]
- position = kwargs["position"]
- del kwargs["position"]
- kwargs = kwargs_to_string(kwargs)
- return element.axes or element.figure, f".text({position[0]:.4f}, {position[1]:.4f}, {repr(text)}, transform={transform}, {kwargs}) # id={getReference(element)}.new"
- else:
- if "position" in kwargs:
- kwargs["position"] = tuple(np.round(kwargs['position'], 4))
- kwargs = kwargs_to_string(kwargs)
- return element, f".set({kwargs})"
- elif isinstance(element, Legend):
- ncols_name = "ncols"
- if version.parse(mpl._get_version()) < version.parse("3.6.0"):
- ncols_name = "ncol"
-
- property_names = [
- ("frameon", lambda x: x.get_frame_on()),
- ("borderpad", lambda x: x.borderpad),
- ("labelspacing", lambda x: x.labelspacing),
- ("markerscale", lambda x: x.markerscale),
- ("handlelength", lambda x: x.handlelength),
- ("handletextpad", lambda x: x.handletextpad),
- (ncols_name, lambda x: getattr(x, "_" + ncols_name)),
- ("columnspacing", lambda x: x.columnspacing),
- ("fontsize", lambda x: x._fontsize),
- ("title", lambda x: x.get_title().get_text()),
- ("title_fontsize", lambda x: x.get_title().get_fontsize()),
- ]
-
- # get current property values
- kwargs = {"loc": element._loc}
- for prop, func in property_names:
- value = func(element)
- try:
- default = plt.rcParams["legend." + prop]
- except KeyError:
- if prop == "title":
- default = ""
- elif prop == ncols_name:
- default = 1
- else:
- default = None
- pass
- if (prop == "fontsize" or prop == "title_fontsize") and (default == "medium" or default == None):
- if value == plt.rcParams["font.size"]:
- continue
- if prop == "title_fontsize" and "title" not in kwargs:
- continue
- if value != default or not exclude_default:
- kwargs[prop] = value
- parent = element.figure if element.axes is None else element.axes
- return parent, f".legend({kwargs_to_string(kwargs)})"
- elif isinstance(element, Axes):
- properties = ["position",
- "xscale", "xlabel", "xticks", "xticklabels", "xlim",
- "yscale", "ylabel", "yticks", "yticklabels", "ylim",
- "zorder"
- ]
-
- # get current property values
- kwargs = {}
- for prop in properties:
- value = getattr(element, f"get_{prop}")()
- #if self.text_properties_defaults[prop] != value or not exclude_default:
- kwargs[prop] = value
-
- pos = element.get_position()
- kwargs["position"] = [pos.x0, pos.y0, pos.width, pos.height]
- kwargs["xticks"] = list(kwargs["xticks"])
- kwargs["xticklabels"] = [t.get_text() for t in kwargs["xticklabels"]]
- kwargs["yticks"] = list(kwargs["yticks"])
- kwargs["yticklabels"] = [t.get_text() for t in kwargs["yticklabels"]]
- kwargs["xticks-minor"] = list(element.get_xticks(minor=True))
- kwargs["xticklabels-minor"] = [t.get_text() for t in element.get_xticklabels(minor=True)]
- kwargs["yticks-minor"] = list(element.get_yticks(minor=True))
- kwargs["yticklabels-minor"] = [t.get_text() for t in element.get_yticklabels(minor=True)]
- from matplotlib.ticker import AutoLocator
- if 0:
- if element.get_autoscale_on():
- del kwargs["xlim"]
- del kwargs["ylim"]
- if isinstance(element.get_xaxis().major.locator, AutoLocator):
- del kwargs["xticks"]
- del kwargs["xticklabels"]
- if isinstance(element.get_xaxis().major.locator, AutoLocator) and element._pylustrator_old_args["xticks-locator"]:
- del kwargs["xticks"]
- del kwargs["xticklabels"]
- if isinstance(element.get_yaxis().major.locator, AutoLocator) and element._pylustrator_old_args["yticks-locator"]:
- del kwargs["yticks"]
- del kwargs["yticklabels"]
-
- # get current property values
- for prop in list(kwargs.keys()):
- value = kwargs[prop]
- default = element._pylustrator_old_args[prop]
- if to_str(default) == to_str(value) and exclude_default:
- del kwargs[prop]
-
- # the main properties that can be set directly
- desc_strings = [(element, f".set({kwargs_to_string({k: v for k, v in kwargs.items() if k in properties})})")]
-
- # the minor ticks
- if "xticks-minor" in kwargs:
- desc_strings.append([element, f".set_xticks({to_str(kwargs['xticks-minor'])}, {to_str(kwargs['xticklabels-minor'])}, minor=True)"])
- if "yticks-minor" in kwargs:
- desc_strings.append([element, f".set_yticks({to_str(kwargs['yticks-minor'])}, {to_str(kwargs['yticklabels-minor'])}, minor=True)"])
-
- # the grid
- has_grid = getattr(element.xaxis, "_gridOnMajor", False) or \
- getattr(element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']
- if has_grid != element._pylustrator_old_args["grid"] or not exclude_default:
- desc_strings.append([element, f".grid({has_grid})"])
-
- visible = []
- hidden = []
- for name, spine in element.spines.items():
- if spine.get_visible() != element._pylustrator_old_args["spines"][name] or not exclude_default:
- if spine.get_visible():
- visible.append(name)
- else:
- hidden.append(name)
- if len(visible):
- if version.parse(mpl.__version__) < version.parse("3.4.0"):
- for name in visible:
- desc_strings.append([element, f".spines[{name}].set_visible(True)"])
- else:
- desc_strings.append([element, f".spines[{visible}].set_visible(True)"])
- if len(hidden):
- if version.parse(mpl.__version__) < version.parse("3.4.0"):
- for name in hidden:
- desc_strings.append([element, f".spines[{name}].set_visible(False)"])
- else:
- desc_strings.append([element, f".spines[{hidden}].set_visible(False)"])
-
- return desc_strings
-
- text_properties_defaults = None
- def addNewTextChange(self, element):
- command_parent, command = self.get_describtion_string(element)
-
- # make sure there are no old changes to this element
- keys = [k for k in self.changes]
- for reference_obj, reference_command in keys:
- if reference_obj == element:
- del self.changes[reference_obj, reference_command]
-
- # store the changes
- if getattr(element, "is_new_text", False):
- if not element.get_visible() or element.get_text() == "":
- return
- main_figure(element).change_tracker.addChange(command_parent, command, element, ".new")
- else:
- if command.endswith(".set()"):
- return
- main_figure(element).change_tracker.addChange(command_parent, command)
-
- def addNewLegendChange(self, element):
- command_parent, command = self.get_describtion_string(element)
-
- # make sure there are no old changes to this element
- keys = [k for k in self.changes]
- for reference_obj, reference_command in keys:
- if reference_obj == element:
- del self.changes[reference_obj, reference_command]
-
- # store the changes
- #if not element.get_visible() and getattr(element, "is_new_text", False):
- # return
- main_figure(element).change_tracker.addChange(command_parent, command)
-
- def addNewAxesChange(self, element):
- desc_strings = self.get_describtion_string(element)
-
- # make sure there are no old changes to this element
- keys = [k for k in self.changes]
- for reference_obj, reference_command in keys:
- if reference_obj == element:
- del self.changes[reference_obj, reference_command]
-
- # store the changes
- #if not element.get_visible() and getattr(element, "is_new_text", False):
- # return
- for command_parent, command in desc_strings:
- if command.endswith(".set()"):
- continue
- main_figure(element).change_tracker.addChange(command_parent, command)
-
- def changeCountChanged(self):
- if self.update_changes_signal is not None:
- name_undo = ""
- if self.last_edit >= 0 and len(self.edits[self.last_edit]) > 2:
- name_undo = self.edits[self.last_edit][2]
- name_redo = ""
- if self.last_edit < len(self.edits) - 1 and len(self.edits[self.last_edit+1]) > 2:
- name_redo = self.edits[self.last_edit+1][2]
- self.update_changes_signal.emit(self.last_edit < 0, self.last_edit >= len(self.edits) - 1, name_undo, name_redo)
-
- def removeElement(self, element: Artist):
- """ remove an Artis from the figure """
- # create_key = key+".new"
- created_by_pylustrator = (element, ".new") in self.changes
- # delete changes related to this element
- keys = [k for k in self.changes]
- for reference_obj, reference_command in keys:
- if reference_obj == element:
- del self.changes[reference_obj, reference_command]
- if not created_by_pylustrator or isinstance(element, Text):
- is_label = np.any([ax.xaxis.get_label() == element or ax.yaxis.get_label() == element for ax in element.figure.axes])
- if is_label:
- text_content = element.get_text()
-
- def redo():
- if is_label:
- element.set_text("")
- else:
- element.set_visible(False)
- if isinstance(element, Text):
- main_figure(element).change_tracker.addNewTextChange(element)
- else:
- self.addChange(element, ".set(visible=False)")
-
- def undo():
- if is_label:
- element.set_text(text_content)
- else:
- element.set_visible(True)
- if isinstance(element, Text):
- main_figure(element).change_tracker.addNewTextChange(element)
- else:
- self.addChange(element, ".set(visible=True)")
-
- redo()
- main_figure(element).change_tracker.addEdit([undo, redo, "Delete Text"])
- else:
- element.remove()
- self.figure.selection.remove_target(element)
-
- def addEdit(self, edit: list):
- """ add an edit to the stored list of edits """
- if self.last_edit < len(self.edits) - 1:
- self.edits = self.edits[:self.last_edit + 1]
- self.edits.append(edit)
- self.last_edit = len(self.edits) - 1
- self.last_edit = len(self.edits) - 1
- #print("addEdit", len(self.edits), self.last_edit)
- self.saved = False
- self.changeCountChanged()
-
- def backEdit(self):
- """ undo an edit in the list """
- if self.last_edit < 0:
- #print("no backEdit", len(self.edits), self.last_edit)
- return
- edit = self.edits[self.last_edit]
- edit[0]()
- self.last_edit -= 1
- self.figure.canvas.draw()
- #print("backEdit", len(self.edits), self.last_edit)
- self.changeCountChanged()
-
- def forwardEdit(self):
- """ redo an edit """
- if self.last_edit >= len(self.edits) - 1:
- #print("no forwardEdit", len(self.edits), self.last_edit)
- return
- edit = self.edits[self.last_edit + 1]
- edit[1]()
- self.last_edit += 1
- self.figure.canvas.draw()
- #print("forwardEdit", len(self.edits), self.last_edit)
- self.changeCountChanged()
-
- def load(self):
- """ load a set of changes from a script file. The changes are the code that pylustrator generated """
- regex = re.compile(r"(\.[^\(= ]*)(.*)")
- command_obj_regexes = [getReference(self.figure),
- r"plt\.figure\([^)]*\)",
- r"fig",
- r"\.subfigs\[\d*\]",
- r"\.ax_dict\[\"[^\"]*\"\]",
- r"\.axes\[\d*\]",
- r"\.texts\[\d*\]",
- r"\.{title|_left_title|_right_title}",
- r"\.lines\[\d*\]",
- r"\.collections\[\d*\]",
- r"\.patches\[\d*\]",
- r"\.get_[xy]axis\(\)\.get_(major|minor)_ticks\(\)\[\d*\]",
- r"\.get_[xy]axis\(\)\.get_label\(\)",
- r"\.get_legend\(\)",
- ]
- command_obj_regexes = [re.compile(r) for r in command_obj_regexes]
-
- fig = self.figure
- header = []
- header += ["fig = plt.figure(%s)" % self.figure.number]
- header += ["import matplotlib as mpl"]
-
- self.get_reference_cached = {}
-
- block, lineno = getTextFromFile(getReference(self.figure), stack_position)
- if not block:
- block, lineno = getTextFromFile(getReference(self.figure, allow_using_variable_names=False), stack_position)
- for line in block:
- try:
- line = line.strip()
- lineno += 1
- if line == "" or line in header or line.startswith("#"):
- continue
- if re.match(r".*\.ax_dict =.*", line):
- continue
-
- raw_line = line
-
- # try to identify the command object of the line
- command_obj = ""
- for r in command_obj_regexes:
- while True:
- try:
- found = r.match(line).group()
- line = line[len(found):]
- command_obj += found
- except AttributeError:
- pass
- break
-
- try:
- command, parameter = regex.match(line).groups()
- except AttributeError: # no regex match
- continue
-
- m = re.match(r".*# id=(.*)", line)
- if m:
- key = m.groups()[0]
- else:
- key = command_obj + command
-
- # by default reference and command object are the same
- reference_obj = command_obj
- reference_command = command
-
- if command == ".set_xticks" or command == ".set_yticks" or command == ".set_xlabels" or command == ".set_ylabels":
- if line.find("minor=True") != -1:
- reference_command = command + "_minor"
-
- # for new created texts, the reference object is the text and not the axis/figure
- if command == ".text" or command == ".annotate" or command == ".add_patch":
- # for texts we stored the linenumbers where they were created
- if command == ".text":
- # get the parent object (usually an Axes)
- parent = eval(reference_obj)
- # iterate over the texts
- for t in parent.texts:
- # and find if one of the texts was created in the line we are currently looking at
- if getattr(t, "_pylustrator_reference", None):
- if t._pylustrator_reference["stack_position"].lineno == lineno:
- reference_obj = getReference(t)
- break
- else:
- reference_obj, _ = re.match(r"(.*)(\..*)", key).groups()
- else:
- reference_obj, _ = re.match(r"(.*)(\..*)", key).groups()
- reference_command = ".new"
- if command == ".text":
- eval(reference_obj).is_new_text = True
-
- command_obj = eval(command_obj)
- reference_obj_str = reference_obj
- reference_obj = eval(reference_obj)
-
- # if values where saved during the pylustrator saved code
- for change in getattr(reference_obj, "_pylustrator_old_values", []):
- if change["stack_position"].lineno == lineno:
- old_values = change["old_args"].copy()
- old_values.update(getattr(reference_obj, "_pylustrator_old_args", {}))
- reference_obj._pylustrator_old_args = old_values
-
- # if the reference object is just a dummy, we ignore it
- if isinstance(reference_obj, Dummy):
- print("WARNING: line references a missing object, will remove line on save:", raw_line, file=sys.stderr)
- continue
- # this error can occur if there are old saved lines that reference objects that are not there anymore
- except IndexError:
- continue
-
- self.get_reference_cached[reference_obj] = reference_obj_str
-
- #print("---", [reference_obj, reference_command], (command_obj, command + parameter))
- self.changes[reference_obj, reference_command] = (command_obj, command + parameter)
- self.sorted_changes()
-
- def sorted_changes(self):
- """ sort the changes by their priority. For example setting to logscale needs to be executed before xlim. """
- def getRef(obj):
- try:
- return getReference(obj)
- except (ValueError, TypeError):
- # the ticks objects can for some reason not be referenced properly in the next code run
- # they are somehow XTicks objects when loading but when saving they are Text objects
- if obj in self.get_reference_cached:
- return self.get_reference_cached[obj]
- raise
-
- indices = []
- for reference_obj, reference_command in self.changes:
- try:
- if isinstance(reference_obj, Figure):
- obj_indices = ("", "", "", "")
- else:
- if getattr(reference_obj, "axes", None) is not None and not isinstance(getattr(reference_obj, "axes", None), list):
- if reference_command == ".new":
- index = "0"
- elif reference_command == ".set_xscale" or reference_command == ".set_yscale":
- index = "1"
- elif reference_command == ".set_xlim" or reference_command == ".set_ylim":
- index = "2"
- elif reference_command == ".set_xticks" or reference_command == ".set_yticks":
- index = "3"
- elif reference_command == ".set_xticklabels" or reference_command == ".set_yticklabels":
- index = "4"
- else:
- index = "5"
- obj_indices = (getRef(reference_obj.axes), getRef(reference_obj), index, reference_command)
- else:
- obj_indices = (getRef(reference_obj), "", "", reference_command)
- indices.append(
- [(reference_obj, reference_command), self.changes[reference_obj, reference_command], obj_indices])
- except (ValueError, TypeError) as err:
- print(err, file=sys.stderr)
-
- srt = natsorted(indices, key=lambda a: a[2])
- output = []
- for s in srt:
- command_obj, command = s[1]
- try:
- output.append(getRef(command_obj) + command)
- except TypeError as err:
- print(err, file=sys.stderr)
-
- return output
-
- def save(self):
- """ save the changes to the .py file """
- # if saving is disabled
- if self.no_save is True:
- return
- header = [
- getReference(self.figure) + ".ax_dict = {ax.get_label(): ax for ax in " + getReference(self.figure) + ".axes}",
- "import matplotlib as mpl",
- f"getattr({getReference(self.figure)}, '_pylustrator_init', lambda: ...)()",
- ]
-
- # block = getTextFromFile(header[0], self.stack_position)
- output = [custom_prepend + "#% start: automatic generated code from pylustrator"]
- # add the lines from the header
- for line in header:
- output.append(line)
- # add all lines from the changes
- for line in self.sorted_changes():
- output.append(line)
- if line.startswith("fig.add_axes"):
- output.append(header[1])
- output.append("#% end: automatic generated code from pylustrator" + custom_append)
- print("\n"+"\n".join(output)+"\n")
-
- block_id = getReference(self.figure)
- block = getTextFromFile(block_id, stack_position)
- if not block:
- block_id = getReference(self.figure, allow_using_variable_names=False)
- block = getTextFromFile(block_id, stack_position)
- try:
- insertTextToFile(output, stack_position, block_id)
- except FileNotFoundError:
- print(
- "WARNING: no file to save the above changes was found, you are probably using pylustrator from a shell session.",
- file=sys.stderr)
- self.saved = True
-
-
-def getTextFromFile(block_id: str, stack_pos: traceback.FrameSummary):
- """ get the text which corresponds to the block_id (e.g. which figure) at the given position sepcified by stack_pos. """
- block_id = lineToId(block_id)
- block = None
-
- if not custom_stack_position:
- if not stack_pos.filename.endswith('.py') and not stack_pos.filename.startswith("
+
+import re
+import sys
+import traceback
+from typing import IO, Optional, Dict, Tuple, Callable, cast
+from packaging import version
+
+import numpy as np
+import matplotlib
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from matplotlib import _pylab_helpers
+from matplotlib.artist import Artist
+from matplotlib.axes import Axes
+from matplotlib.figure import Figure
+from matplotlib.text import Text
+from matplotlib.legend import Legend
+from matplotlib.collections import Collection
+from matplotlib.patches import Patch
+from matplotlib.lines import Line2D
+
+try:
+ from matplotlib.figure import SubFigure # since matplotlib 3.4.0
+except ImportError:
+ SubFigure = None # type: ignore[assignment]
+
+try:
+ from natsort import natsorted
+except ImportError:
+ natsorted: Callable = sorted
+
+from .exception_swallower import Dummy
+from .jupyter_cells import open
+from .helper_functions import main_figure
+
+""" External overload """
+
+
+class CustomStackPosition:
+ filename = None
+ lineno = None
+
+ def __init__(self, filename, lineno):
+ self.filename = filename
+ self.lineno = lineno
+
+
+custom_stack_position = None
+custom_prepend = ""
+custom_append = ""
+
+stack_position: traceback.FrameSummary | None = None
+
+escape_pairs = [
+ ("\\", "\\\\"),
+ ("\n", "\\n"),
+ ("\r", "\\r"),
+ ('"', '\\"'),
+]
+
+
+def escape_string(str):
+ for pair in escape_pairs:
+ str = str.replace(pair[0], pair[1])
+ return str
+
+
+def unescape_string(str):
+ for pair in escape_pairs:
+ str = str.replace(pair[1], pair[0])
+ return str
+
+
+def to_str(v):
+ if isinstance(v, list) and len(v) and isinstance(v[0], float):
+ return (
+ "["
+ + ", ".join(
+ np.format_float_positional(a, 4, fractional=False, trim=".") for a in v
+ )
+ + "]"
+ )
+ elif isinstance(v, tuple) and len(v) and isinstance(v[0], float):
+ return (
+ "("
+ + ", ".join(
+ np.format_float_positional(a, 4, fractional=False, trim=".") for a in v
+ )
+ + ")"
+ )
+ elif isinstance(v, float):
+ return np.format_float_positional(v, 4, fractional=False, trim=".")
+ return repr(v)
+
+
+def kwargs_to_string(kwargs):
+ return ", ".join(f"{k}={to_str(v)}" for k, v in kwargs.items())
+
+
+class UndoRedo:
+ def __init__(self, elements, name):
+ self.elements = list(elements)
+ self.name = name
+ if len(elements):
+ self.figure = main_figure(elements[0])
+ self.change_tracker = self.figure.change_tracker
+
+ def __enter__(self):
+ if len(self.elements):
+ self.undo = self.change_tracker.get_element_restore_function(self.elements)
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if len(self.elements):
+ self.redo = self.change_tracker.get_element_restore_function(self.elements)
+ self.redo()
+ self.figure.canvas.draw()
+ self.figure.signals.figure_selection_property_changed.emit()
+ self.change_tracker.addEdit([self.undo, self.redo, self.name])
+
+
+def init_figure(fig):
+ for axes in fig.axes:
+ add_axes_default(axes)
+ add_text_default(axes.title)
+ add_text_default(axes._left_title)
+ add_text_default(axes._right_title)
+ for text in axes.texts:
+ add_text_default(text)
+ for text in fig.texts:
+ add_text_default(text)
+ for subfig in fig.subfigs:
+ for text in subfig.texts:
+ add_text_default(text)
+
+
+def add_text_default(element):
+ # properties to store
+ properties = [
+ "position",
+ "text",
+ "ha",
+ "va",
+ "fontsize",
+ "color",
+ "style",
+ "weight",
+ "fontname",
+ "rotation",
+ ]
+
+ # if the default properties are not set, aquire them
+ if getattr(element, "_pylustrator_old_args", None) is None:
+ old_args = {}
+ for name in properties:
+ try:
+ old_args[name] = getattr(element, f"get_{name}")()
+ except AttributeError:
+ continue
+ if getattr(element, "is_new_text", False):
+ # For new text elements, use matplotlib's default text properties
+ # so that any customizations will be detected and saved
+ old_args["position"] = None
+ old_args["text"] = None
+ old_args["ha"] = "left"
+ old_args["va"] = "baseline"
+ old_args["fontsize"] = 10.0
+ old_args["color"] = "black"
+ old_args["style"] = "normal"
+ old_args["weight"] = "normal"
+ old_args["fontname"] = "DejaVu Sans"
+ old_args["rotation"] = 0.0
+ element._pylustrator_old_args = old_args
+
+
+def add_axes_default(element):
+ properties = [
+ "position",
+ "xlim",
+ "xlabel",
+ "xticks",
+ "xticklabels",
+ "xscale",
+ "ylim",
+ "ylabel",
+ "yticks",
+ "yticklabels",
+ "yscale",
+ "zorder",
+ ]
+ if getattr(element, "_pylustrator_old_args", None) is None:
+ old_args = {}
+ for name in properties:
+ try:
+ old_args[name] = getattr(element, f"get_{name}")()
+ except AttributeError:
+ continue
+ pos = element.get_position()
+ old_args["position"] = [pos.x0, pos.y0, pos.width, pos.height]
+ old_args["xticks"] = list(old_args["xticks"])
+ old_args["xticks-locator"] = element.get_xaxis().major.locator
+ old_args["xticklabels"] = [t.get_text() for t in old_args["xticklabels"]]
+ old_args["yticks"] = list(old_args["yticks"])
+ old_args["yticks-locator"] = element.get_yaxis().major.locator
+ old_args["yticklabels"] = [t.get_text() for t in old_args["yticklabels"]]
+ old_args["grid"] = (
+ getattr(element.xaxis, "_gridOnMajor", False)
+ or getattr(element.xaxis, "_major_tick_kw", {"gridOn": False})["gridOn"]
+ )
+ old_args["spines"] = {s: v.get_visible() for s, v in element.spines.items()}
+
+ old_args["xticks-minor"] = list(element.get_xticks(minor=True))
+ old_args["xticklabels-minor"] = [
+ t.get_text() for t in element.get_xticklabels(minor=True)
+ ]
+ old_args["yticks-minor"] = list(element.get_yticks(minor=True))
+ old_args["yticklabels-minor"] = [
+ t.get_text() for t in element.get_yticklabels(minor=True)
+ ]
+ element._pylustrator_old_args = old_args
+ add_text_default(element.get_xaxis().get_label())
+ add_text_default(element.get_yaxis().get_label())
+
+
+def getReference(element: Artist | Figure | SubFigure, allow_using_variable_names=True):
+ """get the code string that represents the given Artist."""
+ if element is None:
+ return ""
+ if isinstance(element, Figure):
+ if allow_using_variable_names:
+ name = getattr(element, "_variable_name", None)
+ if name is not None:
+ return name
+ if isinstance(element.number, (float, int)):
+ return "plt.figure(%s)" % element.number
+ else:
+ return 'plt.figure("%s")' % element.number
+ # subfigures are only available in matplotlib>=3.4.0
+ if version.parse(mpl.__version__) >= version.parse("3.4.0") and isinstance(
+ element, SubFigure
+ ):
+ index = element._parent.subfigs.index(element)
+ return getReference(element._parent) + ".subfigs[%d]" % index
+ if isinstance(element, Line2D):
+ axes = element.axes
+ if not isinstance(axes, Axes):
+ raise ValueError("element must be a matplotlib.axes.Axes instance")
+ index = axes.lines.index(element)
+ return getReference(axes) + ".lines[%d]" % index
+ if isinstance(element, Collection):
+ axes = element.axes
+ if not isinstance(axes, Axes):
+ raise ValueError("element must be a matplotlib.axes.Axes instance")
+ index = axes.collections.index(element)
+ return getReference(axes) + ".collections[%d]" % index
+ if isinstance(element, Patch):
+ if element.axes:
+ index = element.axes.patches.index(element)
+ return getReference(element.axes) + ".patches[%d]" % index
+ index = element.figure.patches.index(element)
+ return getReference(element.figure) + ".patches[%d]" % (index)
+
+ if isinstance(element, Text):
+ if element.axes:
+ try:
+ index = element.axes.texts.index(element)
+ except ValueError:
+ for attribute_name in ["title", "_left_title", "_right_title"]:
+ if getattr(element.axes, attribute_name, None) == element:
+ return getReference(element.axes) + "." + attribute_name
+ pass
+ else:
+ return getReference(element.axes) + ".texts[%d]" % index
+ try:
+ index = element.figure.texts.index(element)
+ return getReference(element.figure) + ".texts[%d]" % (index)
+ except ValueError:
+ pass
+ for axes in element.figure.axes:
+ if element == axes.get_xaxis().get_label():
+ return getReference(axes) + ".get_xaxis().get_label()"
+ if element == axes.get_yaxis().get_label():
+ return getReference(axes) + ".get_yaxis().get_label()"
+
+ for index, label in enumerate(axes.get_xaxis().get_major_ticks()):
+ if element == label.label1:
+ return (
+ getReference(axes)
+ + ".get_xaxis().get_major_ticks()[%d].label1" % index
+ )
+ if element == label.label2:
+ return (
+ getReference(axes)
+ + ".get_xaxis().get_major_ticks()[%d].label2" % index
+ )
+ for index, label in enumerate(axes.get_xaxis().get_minor_ticks()):
+ if element == label.label1:
+ return (
+ getReference(axes)
+ + ".get_xaxis().get_minor_ticks()[%d].label1" % index
+ )
+ if element == label.label2:
+ return (
+ getReference(axes)
+ + ".get_xaxis().get_minor_ticks()[%d].label2" % index
+ )
+
+ for axes in element.figure.axes:
+ for index, label in enumerate(axes.get_yaxis().get_major_ticks()):
+ if element == label.label1:
+ return (
+ getReference(axes)
+ + ".get_yaxis().get_major_ticks()[%d].label1" % index
+ )
+ if element == label.label2:
+ return (
+ getReference(axes)
+ + ".get_yaxis().get_major_ticks()[%d].label2" % index
+ )
+ for index, label in enumerate(axes.get_yaxis().get_minor_ticks()):
+ if element == label.label1:
+ return (
+ getReference(axes)
+ + ".get_yaxis().get_minor_ticks()[%d].label1" % index
+ )
+ if element == label.label2:
+ return (
+ getReference(axes)
+ + ".get_yaxis().get_minor_ticks()[%d].label2" % index
+ )
+
+ if isinstance(element, Axes):
+ if element.get_label():
+
+ def check_fig_has_label(fig):
+ for ax in element.figure.axes:
+ if ax != element and ax.get_label() == element.get_label():
+ return True
+ return False
+
+ if not check_fig_has_label(element.figure):
+ return getReference(element.figure) + '.ax_dict["%s"]' % escape_string(
+ element.get_label()
+ )
+ index = element.figure.axes.index(element)
+ return getReference(element.figure) + ".axes[%d]" % index
+
+ if isinstance(element, Legend):
+ return getReference(element.axes) + ".get_legend()"
+ raise TypeError(str(type(element)) + " not found")
+
+
+def setFigureVariableNames(figure: Figure):
+ """get the global variable names that refer to the given figure"""
+ import inspect
+
+ mpl_figure = _pylab_helpers.Gcf.figs[figure].canvas.figure # ty:ignore[invalid-argument-type]
+ calling_globals = inspect.stack()[2][0].f_globals
+ fig_names = [
+ name
+ for name, val in calling_globals.items()
+ if isinstance(val, Figure) and hash(val) == hash(mpl_figure)
+ ]
+ # print("fig_names", fig_names)
+ if len(fig_names):
+ globals()[fig_names[0]] = mpl_figure
+ setattr(mpl_figure, "_variable_name", fig_names[0])
+
+
+class ChangeTracker:
+ """a class that records a list of the change to the figure"""
+
+ changes: Dict[Tuple[Artist, str], Tuple[Artist, str]]
+ saved = True
+
+ update_changes_signal = None
+
+ def __init__(self, figure: Figure, no_save):
+ global stack_position
+ if not isinstance(figure, Figure):
+ raise ValueError("figure must be a matplotlib figure")
+ self.figure: Figure = figure
+ self.edits = []
+ self.last_edit = -1
+ self.changes: Dict[Tuple[Artist, str], Tuple[Artist, str]] = {}
+ self.no_save = no_save
+
+ # make all the subplots pickable
+ for index, axes in enumerate(self.figure.axes):
+ # axes.set_title(index)
+ setattr(axes, "_pylustrator_number", index)
+
+ # store the position where StartPylustrator was called
+ if custom_stack_position is None:
+ stack_position = traceback.extract_stack()[-4]
+ else:
+ stack_position = custom_stack_position
+
+ self.fig_inch_size = self.figure.get_size_inches()
+
+ self.load()
+
+ def addChange(
+ self,
+ command_obj: Artist,
+ command: str,
+ reference_obj: Optional[Artist] = None,
+ reference_command: Optional[str] = None,
+ ):
+ """add a change"""
+ command = command.replace("\n", "\\n")
+ if reference_obj is None:
+ reference_obj = command_obj
+ if reference_command is None:
+ match = re.match(r"(\.[^(=]*)", command)
+ if match is not None:
+ (reference_command,) = match.groups()
+ else:
+ raise ValueError("command must start with .")
+ self.changes[reference_obj, reference_command] = (command_obj, command)
+ self.saved = False
+ self.changeCountChanged()
+
+ def get_element_restore_function(self, elements):
+ description_strings = []
+ for element in elements:
+ desc = self.get_describtion_string(element, exclude_default=False)
+ if isinstance(desc, list):
+ description_strings.extend(desc)
+ else:
+ description_strings.append(desc)
+
+ def restore():
+ for element, string in description_strings:
+ # function, arguments = re.match(r"\.([^(]*)\((.*)\)", string)
+ print(f"eval {getReference(element)}{string}")
+ eval(f"{getReference(element)}{string}")
+ if isinstance(element, Text):
+ self.addNewTextChange(element)
+ elif isinstance(element, Axes) and string.startswith(".legend("):
+ self.addNewLegendChange(element.get_legend())
+ elif isinstance(element, Axes):
+ self.addNewAxesChange(element)
+ else:
+ raise NotImplementedError
+ # getattr(element, function)(eval(arg))
+
+ return restore
+
+ def get_describtion_string(self, element, exclude_default=True):
+ if isinstance(element, Text):
+ # if the text is deleted we do not need to store all properties
+ if not element.get_visible() or element.get_text() == "":
+ if getattr(element, "is_new_text", False):
+ return (
+ element.axes or element.figure,
+ ".text(0, 0, , visible=False)",
+ )
+ else:
+ is_label = np.any(
+ [
+ ax.xaxis.get_label() == element
+ or ax.yaxis.get_label() == element
+ for ax in element.figure.axes
+ ]
+ )
+ if is_label:
+ return element, ".set(text='')"
+ return element, ".set(visible=False)"
+
+ # properties to store
+ properties = [
+ "position",
+ "text",
+ "ha",
+ "va",
+ "fontsize",
+ "color",
+ "style",
+ "weight",
+ "fontname",
+ "rotation",
+ ]
+
+ # get current property values
+ kwargs = {}
+ for prop in properties:
+ value = getattr(element, f"get_{prop}")()
+ default = element._pylustrator_old_args[prop]
+ if to_str(default) != to_str(value) or not exclude_default:
+ kwargs[prop] = value
+
+ # get position and transformation
+ pos = element.get_position()
+ if element.axes:
+ transform = getReference(element.axes) + ".transAxes"
+ else:
+ transform = getReference(element.figure) + ".transFigure"
+
+ # compose text
+ if getattr(element, "is_new_text", False) and exclude_default:
+ text = kwargs.pop("text", element.get_text())
+ position = kwargs.pop("position", element.get_position())
+ kwargs = kwargs_to_string(kwargs)
+ return (
+ element.axes or element.figure,
+ f".text({position[0]:.4f}, {position[1]:.4f}, {repr(text)}, transform={transform}, {kwargs}) # id={getReference(element)}.new",
+ )
+ else:
+ if "position" in kwargs:
+ kwargs["position"] = tuple(np.round(kwargs["position"], 4))
+ kwargs = kwargs_to_string(kwargs)
+ return element, f".set({kwargs})"
+ elif isinstance(element, Legend):
+ ncols_name = "ncols"
+ if version.parse(mpl.__version__) < version.parse("3.6.0"):
+ ncols_name = "ncol"
+
+ property_names = [
+ ("frameon", lambda x: x.get_frame_on()),
+ ("borderpad", lambda x: x.borderpad),
+ ("labelspacing", lambda x: x.labelspacing),
+ ("markerscale", lambda x: x.markerscale),
+ ("handlelength", lambda x: x.handlelength),
+ ("handletextpad", lambda x: x.handletextpad),
+ (ncols_name, lambda x: getattr(x, "_" + ncols_name)),
+ ("columnspacing", lambda x: x.columnspacing),
+ ("fontsize", lambda x: x._fontsize),
+ ("title", lambda x: x.get_title().get_text()),
+ ("title_fontsize", lambda x: x.get_title().get_fontsize()),
+ ]
+
+ # get current property values
+ kwargs = {"loc": element._loc}
+ for prop, func in property_names:
+ value = func(element)
+ try:
+ default = plt.rcParams["legend." + prop]
+ except KeyError:
+ if prop == "title":
+ default = ""
+ elif prop == ncols_name:
+ default = 1
+ else:
+ default = None
+ pass
+ if (prop == "fontsize" or prop == "title_fontsize") and (
+ default == "medium" or default is None
+ ):
+ if value == plt.rcParams["font.size"]:
+ continue
+ if prop == "title_fontsize" and "title" not in kwargs:
+ continue
+ if value != default or not exclude_default:
+ kwargs[prop] = value
+ parent = element.figure if element.axes is None else element.axes
+ return parent, f".legend({kwargs_to_string(kwargs)})"
+ elif isinstance(element, Axes):
+ properties = [
+ "position",
+ "xscale",
+ "xlabel",
+ "xticks",
+ "xticklabels",
+ "xlim",
+ "yscale",
+ "ylabel",
+ "yticks",
+ "yticklabels",
+ "ylim",
+ "zorder",
+ ]
+
+ # get current property values
+ kwargs = {}
+ for prop in properties:
+ value = getattr(element, f"get_{prop}")()
+ # if self.text_properties_defaults[prop] != value or not exclude_default:
+ kwargs[prop] = value
+
+ pos = element.get_position()
+ kwargs["position"] = [pos.x0, pos.y0, pos.width, pos.height]
+ kwargs["xticks"] = list(kwargs["xticks"])
+ kwargs["xticklabels"] = [t.get_text() for t in kwargs["xticklabels"]]
+ kwargs["yticks"] = list(kwargs["yticks"])
+ kwargs["yticklabels"] = [t.get_text() for t in kwargs["yticklabels"]]
+ kwargs["xticks-minor"] = list(element.get_xticks(minor=True))
+ kwargs["xticklabels-minor"] = [
+ t.get_text() for t in element.get_xticklabels(minor=True)
+ ]
+ kwargs["yticks-minor"] = list(element.get_yticks(minor=True))
+ kwargs["yticklabels-minor"] = [
+ t.get_text() for t in element.get_yticklabels(minor=True)
+ ]
+ from matplotlib.ticker import AutoLocator
+
+ if 0:
+ if element.get_autoscale_on():
+ del kwargs["xlim"]
+ del kwargs["ylim"]
+ if isinstance(element.get_xaxis().major.locator, AutoLocator):
+ del kwargs["xticks"]
+ del kwargs["xticklabels"]
+ if (
+ isinstance(element.get_xaxis().major.locator, AutoLocator)
+ and element._pylustrator_old_args["xticks-locator"]
+ ):
+ del kwargs["xticks"]
+ del kwargs["xticklabels"]
+ if (
+ isinstance(element.get_yaxis().major.locator, AutoLocator)
+ and element._pylustrator_old_args["yticks-locator"]
+ ):
+ del kwargs["yticks"]
+ del kwargs["yticklabels"]
+
+ # get current property values
+ for prop in list(kwargs.keys()):
+ value = kwargs[prop]
+ default = element._pylustrator_old_args[prop]
+ if to_str(default) == to_str(value) and exclude_default:
+ del kwargs[prop]
+
+ # the main properties that can be set directly
+ desc_strings = [
+ (
+ element,
+ f".set({kwargs_to_string({k: v for k, v in kwargs.items() if k in properties})})",
+ )
+ ]
+
+ # the minor ticks
+ if "xticks-minor" in kwargs:
+ desc_strings.append(
+ [
+ element,
+ f".set_xticks({to_str(kwargs['xticks-minor'])}, {to_str(kwargs['xticklabels-minor'])}, minor=True)",
+ ]
+ )
+ if "yticks-minor" in kwargs:
+ desc_strings.append(
+ [
+ element,
+ f".set_yticks({to_str(kwargs['yticks-minor'])}, {to_str(kwargs['yticklabels-minor'])}, minor=True)",
+ ]
+ )
+
+ # the grid
+ has_grid = (
+ getattr(element.xaxis, "_gridOnMajor", False)
+ or getattr(element.xaxis, "_major_tick_kw", {"gridOn": False})["gridOn"]
+ )
+ if has_grid != element._pylustrator_old_args["grid"] or not exclude_default:
+ desc_strings.append([element, f".grid({has_grid})"])
+
+ visible = []
+ hidden = []
+ for name, spine in element.spines.items():
+ if (
+ spine.get_visible() != element._pylustrator_old_args["spines"][name]
+ or not exclude_default
+ ):
+ if spine.get_visible():
+ visible.append(name)
+ else:
+ hidden.append(name)
+ if len(visible):
+ if version.parse(mpl.__version__) < version.parse("3.4.0"):
+ for name in visible:
+ desc_strings.append(
+ [element, f".spines[{name}].set_visible(True)"]
+ )
+ else:
+ desc_strings.append(
+ [element, f".spines[{visible}].set_visible(True)"]
+ )
+ if len(hidden):
+ if version.parse(mpl.__version__) < version.parse("3.4.0"):
+ for name in hidden:
+ desc_strings.append(
+ [element, f".spines[{name}].set_visible(False)"]
+ )
+ else:
+ desc_strings.append(
+ [element, f".spines[{hidden}].set_visible(False)"]
+ )
+
+ return desc_strings
+
+ text_properties_defaults = None
+
+ def addNewTextChange(self, element):
+ command_parent, command = self.get_describtion_string(element)
+
+ # make sure there are no old changes to this element
+ keys = [k for k in self.changes]
+ for reference_obj, reference_command in keys:
+ if reference_obj == element:
+ del self.changes[reference_obj, reference_command]
+
+ # store the changes
+ if getattr(element, "is_new_text", False):
+ if not element.get_visible() or element.get_text() == "":
+ return
+ main_figure(element).change_tracker.addChange(
+ command_parent, command, element, ".new"
+ )
+ else:
+ if command.endswith(".set()"):
+ return
+ main_figure(element).change_tracker.addChange(command_parent, command)
+
+ def addNewLegendChange(self, element):
+ command_parent, command = self.get_describtion_string(element)
+
+ # make sure there are no old changes to this element
+ keys = [k for k in self.changes]
+ for reference_obj, reference_command in keys:
+ if reference_obj == element:
+ del self.changes[reference_obj, reference_command]
+
+ # store the changes
+ # if not element.get_visible() and getattr(element, "is_new_text", False):
+ # return
+ main_figure(element).change_tracker.addChange(command_parent, command)
+
+ def addNewAxesChange(self, element):
+ desc_strings = self.get_describtion_string(element)
+
+ # make sure there are no old changes to this element
+ keys = [k for k in self.changes]
+ for reference_obj, reference_command in keys:
+ if reference_obj == element:
+ del self.changes[reference_obj, reference_command]
+
+ # store the changes
+ # if not element.get_visible() and getattr(element, "is_new_text", False):
+ # return
+ for command_parent, command in desc_strings:
+ if command.endswith(".set()"):
+ continue
+ main_figure(element).change_tracker.addChange(command_parent, command)
+
+ def changeCountChanged(self):
+ if self.update_changes_signal is not None:
+ name_undo = ""
+ if self.last_edit >= 0 and len(self.edits[self.last_edit]) > 2:
+ name_undo = self.edits[self.last_edit][2]
+ name_redo = ""
+ if (
+ self.last_edit < len(self.edits) - 1
+ and len(self.edits[self.last_edit + 1]) > 2
+ ):
+ name_redo = self.edits[self.last_edit + 1][2]
+ self.update_changes_signal.emit(
+ self.last_edit < 0,
+ self.last_edit >= len(self.edits) - 1,
+ name_undo,
+ name_redo,
+ )
+
+ def removeElement(self, element: Artist):
+ """remove an Artis from the figure"""
+ # create_key = key+".new"
+ created_by_pylustrator = (element, ".new") in self.changes
+ # delete changes related to this element
+ keys = [k for k in self.changes]
+ for reference_obj, reference_command in keys:
+ if reference_obj == element:
+ del self.changes[reference_obj, reference_command]
+ if not created_by_pylustrator or isinstance(element, Text):
+ is_label = np.any(
+ [
+ ax.xaxis.get_label() == element or ax.yaxis.get_label() == element
+ for ax in element.figure.axes
+ ]
+ )
+ if is_label and isinstance(element, Text):
+ text_content = element.get_text()
+
+ def redo():
+ if is_label and isinstance(element, Text):
+ element.set_text("")
+ else:
+ element.set_visible(False)
+ if isinstance(element, Text):
+ main_figure(element).change_tracker.addNewTextChange(element)
+ else:
+ self.addChange(element, ".set(visible=False)")
+
+ def undo():
+ if is_label and isinstance(element, Text):
+ element.set_text(text_content)
+ else:
+ element.set_visible(True)
+ if isinstance(element, Text):
+ main_figure(element).change_tracker.addNewTextChange(element)
+ else:
+ self.addChange(element, ".set(visible=True)")
+
+ redo()
+ main_figure(element).change_tracker.addEdit([undo, redo, "Delete Text"])
+ else:
+ element.remove()
+ self.figure.selection.remove_target(element)
+
+ def addEdit(self, edit: list):
+ """add an edit to the stored list of edits"""
+ if self.last_edit < len(self.edits) - 1:
+ self.edits = self.edits[: self.last_edit + 1]
+ self.edits.append(edit)
+ self.last_edit = len(self.edits) - 1
+ self.last_edit = len(self.edits) - 1
+ # print("addEdit", len(self.edits), self.last_edit)
+ self.saved = False
+ self.changeCountChanged()
+
+ def backEdit(self):
+ """undo an edit in the list"""
+ if self.last_edit < 0:
+ # print("no backEdit", len(self.edits), self.last_edit)
+ return
+ edit = self.edits[self.last_edit]
+ edit[0]()
+ self.last_edit -= 1
+ self.figure.canvas.draw()
+ # print("backEdit", len(self.edits), self.last_edit)
+ self.changeCountChanged()
+
+ def forwardEdit(self):
+ """redo an edit"""
+ if self.last_edit >= len(self.edits) - 1:
+ # print("no forwardEdit", len(self.edits), self.last_edit)
+ return
+ edit = self.edits[self.last_edit + 1]
+ edit[1]()
+ self.last_edit += 1
+ self.figure.canvas.draw()
+ # print("forwardEdit", len(self.edits), self.last_edit)
+ self.changeCountChanged()
+
+ def load(self):
+ """load a set of changes from a script file. The changes are the code that pylustrator generated"""
+ if stack_position is None:
+ return
+ regex = re.compile(r"(\.[^\(= ]*)(.*)")
+ command_obj_regexes = [
+ getReference(self.figure),
+ r"plt\.figure\([^)]*\)",
+ r"fig",
+ r"\.subfigs\[\d*\]",
+ r"\.ax_dict\[\"[^\"]*\"\]",
+ r"\.axes\[\d*\]",
+ r"\.texts\[\d*\]",
+ r"\.{title|_left_title|_right_title}",
+ r"\.lines\[\d*\]",
+ r"\.collections\[\d*\]",
+ r"\.patches\[\d*\]",
+ r"\.get_[xy]axis\(\)\.get_(major|minor)_ticks\(\)\[\d*\]",
+ r"\.get_[xy]axis\(\)\.get_label\(\)",
+ r"\.get_legend\(\)",
+ ]
+ command_obj_regexes = [re.compile(r) for r in command_obj_regexes]
+
+ # fig = self.figure
+ header = []
+ header += ["fig = plt.figure(%s)" % self.figure.number]
+ header += ["import matplotlib as mpl"]
+
+ self.get_reference_cached = {}
+
+ block, lineno = getTextFromFile(getReference(self.figure), stack_position)
+ if not block:
+ block, lineno = getTextFromFile(
+ getReference(self.figure, allow_using_variable_names=False),
+ stack_position,
+ )
+ for line in block:
+ try:
+ line = line.strip()
+ lineno += 1
+ if line == "" or line in header or line.startswith("#"):
+ continue
+ if re.match(r".*\.ax_dict =.*", line):
+ continue
+
+ raw_line = line
+
+ # try to identify the command object of the line
+ command_obj = ""
+ for r in command_obj_regexes:
+ while True:
+ match = r.match(line)
+ if match:
+ found = match.group()
+ line = line[len(found):]
+ command_obj += found
+ else:
+ break
+
+ match = regex.match(line)
+ if match:
+ command, parameter = match.groups()
+ else:
+ raise ValueError("Could not parse line: %s" % line)
+
+ m = re.match(r".*# id=(.*)", line)
+ if m:
+ key = m.groups()[0]
+ else:
+ key = command_obj + command
+
+ # by default reference and command object are the same
+ reference_obj = command_obj
+ reference_command = command
+
+ if (
+ command == ".set_xticks"
+ or command == ".set_yticks"
+ or command == ".set_xlabels"
+ or command == ".set_ylabels"
+ ):
+ if line.find("minor=True") != -1:
+ reference_command = command + "_minor"
+
+ # for new created texts, the reference object is the text and not the axis/figure
+ if (
+ command == ".text"
+ or command == ".annotate"
+ or command == ".add_patch"
+ ):
+ # for texts we stored the linenumbers where they were created
+ if command == ".text":
+ # get the parent object (usually an Axes)
+ parent = eval(reference_obj)
+ # iterate over the texts
+ for t in parent.texts:
+ # and find if one of the texts was created in the line we are currently looking at
+ if getattr(t, "_pylustrator_reference", None):
+ if (
+ t._pylustrator_reference["stack_position"].lineno
+ == lineno
+ ):
+ reference_obj = getReference(t)
+ break
+ else:
+ match = re.match(r"(.*)(\..*)", key)
+ if match:
+ reference_obj, _ = match.groups()
+ else:
+ match = re.match(r"(.*)(\..*)", key)
+ if match:
+ reference_obj, _ = match.groups()
+ reference_command = ".new"
+ if command == ".text":
+ eval(reference_obj).is_new_text = True
+
+ command_obj = eval(command_obj)
+ reference_obj_str = reference_obj
+ reference_obj = eval(reference_obj)
+
+ # if values where saved during the pylustrator saved code
+ for change in getattr(reference_obj, "_pylustrator_old_values", []):
+ if change["stack_position"].lineno == lineno:
+ old_values = change["old_args"].copy()
+ old_values.update(
+ getattr(reference_obj, "_pylustrator_old_args", {})
+ )
+ reference_obj._pylustrator_old_args = old_values
+
+ # if the reference object is just a dummy, we ignore it
+ if isinstance(reference_obj, Dummy):
+ print(
+ "WARNING: line references a missing object, will remove line on save:",
+ raw_line,
+ file=sys.stderr,
+ )
+ continue
+ # this error can occur if there are old saved lines that reference objects that are not there anymore
+ except (IndexError, ValueError):
+ continue
+
+ self.get_reference_cached[reference_obj] = reference_obj_str
+
+ # print("---", [reference_obj, reference_command], (command_obj, command + parameter))
+ self.changes[reference_obj, reference_command] = (
+ command_obj,
+ command + parameter,
+ )
+ self.sorted_changes()
+
+ def sorted_changes(self):
+ """sort the changes by their priority. For example setting to logscale needs to be executed before xlim."""
+
+ def getRef(obj):
+ try:
+ return getReference(obj)
+ except (ValueError, TypeError):
+ # the ticks objects can for some reason not be referenced properly in the next code run
+ # they are somehow XTicks objects when loading but when saving they are Text objects
+ if obj in self.get_reference_cached:
+ return self.get_reference_cached[obj]
+ raise
+
+ indices = []
+ for reference_obj, reference_command in self.changes:
+ try:
+ if isinstance(reference_obj, Figure):
+ obj_indices = ("", "", "", "")
+ else:
+ if getattr(
+ reference_obj, "axes", None
+ ) is not None and not isinstance(
+ getattr(reference_obj, "axes", None), list
+ ):
+ if reference_command == ".new":
+ index = "0"
+ elif (
+ reference_command == ".set_xscale"
+ or reference_command == ".set_yscale"
+ ):
+ index = "1"
+ elif (
+ reference_command == ".set_xlim"
+ or reference_command == ".set_ylim"
+ ):
+ index = "2"
+ elif (
+ reference_command == ".set_xticks"
+ or reference_command == ".set_yticks"
+ ):
+ index = "3"
+ elif (
+ reference_command == ".set_xticklabels"
+ or reference_command == ".set_yticklabels"
+ ):
+ index = "4"
+ else:
+ index = "5"
+ obj_indices = (
+ getRef(reference_obj.axes),
+ getRef(reference_obj),
+ index,
+ reference_command,
+ )
+ else:
+ obj_indices = (getRef(reference_obj), "", "", reference_command)
+ indices.append(
+ [
+ (reference_obj, reference_command),
+ self.changes[reference_obj, reference_command],
+ obj_indices,
+ ]
+ )
+ except (ValueError, TypeError) as err:
+ print(err, file=sys.stderr)
+
+ srt = natsorted(indices, key=lambda a: a[2])
+ output = []
+ for s in srt:
+ command_obj, command = s[1]
+ try:
+ output.append(getRef(command_obj) + command)
+ except TypeError as err:
+ print(err, file=sys.stderr)
+
+ return output
+
+ def save(self):
+ """save the changes to the .py file"""
+ global stack_position
+ if stack_position is None:
+ return
+
+ # if saving is disabled
+ if self.no_save is True:
+ return
+ header = [
+ getReference(self.figure)
+ + ".ax_dict = {ax.get_label(): ax for ax in "
+ + getReference(self.figure)
+ + ".axes}",
+ "import matplotlib as mpl",
+ f"getattr({getReference(self.figure)}, '_pylustrator_init', lambda: ...)()",
+ ]
+
+ # block = getTextFromFile(header[0], self.stack_position)
+ output = [
+ custom_prepend + "#% start: automatic generated code from pylustrator"
+ ]
+ # add the lines from the header
+ for line in header:
+ output.append(line)
+ # add all lines from the changes
+ for line in self.sorted_changes():
+ output.append(line)
+ if line.startswith("fig.add_axes"):
+ output.append(header[1])
+ output.append(
+ "#% end: automatic generated code from pylustrator" + custom_append
+ )
+ print("\n" + "\n".join(output) + "\n")
+
+ block_id = getReference(self.figure)
+ block = getTextFromFile(block_id, stack_position)
+ if not block:
+ block_id = getReference(self.figure, allow_using_variable_names=False)
+ block = getTextFromFile(block_id, stack_position)
+ try:
+ insertTextToFile(output, stack_position, block_id)
+ except FileNotFoundError:
+ print(
+ "WARNING: no file to save the above changes was found, you are probably using pylustrator from a shell session.",
+ file=sys.stderr,
+ )
+ self.saved = True
+
+
+def getTextFromFile(block_id: str, stack_pos: traceback.FrameSummary):
+ """get the text which corresponds to the block_id (e.g. which figure) at the given position sepcified by stack_pos."""
+ block_id = lineToId(block_id)
+ block = None
+
+ if not custom_stack_position:
+ if not stack_pos.filename.endswith(".py") and not stack_pos.filename.startswith(
+ "Version " + pylustrator.__version__ + "")
font = self.label.font()
font.setPointSize(16)
self.label.setFont(font)
- self.label.setAlignment(QtCore.Qt.AlignCenter)
- self.layout.addWidget(self.label)
+ self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.layout_main.addWidget(self.label)
self.label = QtWidgets.QLabel("Copyright © 2016-2022, Richard Gerum")
- self.label.setAlignment(QtCore.Qt.AlignCenter)
- self.layout.addWidget(self.label)
-
- self.label = QtWidgets.QLabel("Documentation")
- self.label.setAlignment(QtCore.Qt.AlignCenter)
- self.label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.layout_main.addWidget(self.label)
+
+ self.label = QtWidgets.QLabel(
+ "Documentation"
+ )
+ self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.label.setTextInteractionFlags(
+ QtCore.Qt.TextInteractionFlag.TextBrowserInteraction
+ )
self.label.setOpenExternalLinks(True)
- self.layout.addWidget(self.label)
+ self.layout_main.addWidget(self.label)
diff --git a/pylustrator/components/matplotlibwidget.py b/pylustrator/components/matplotlibwidget.py
index 4564984..a5428a7 100644
--- a/pylustrator/components/matplotlibwidget.py
+++ b/pylustrator/components/matplotlibwidget.py
@@ -34,24 +34,40 @@
__version__ = "1.0.0"
-import sys
import time
+from typing import TYPE_CHECKING
import qtawesome as qta
-from matplotlib.backends.qt_compat import QtWidgets, QtCore
+
+if TYPE_CHECKING:
+ from PyQt5 import QtWidgets, QtCore
+ from PyQt5.QtCore import pyqtSignal as Signal
+else:
+ from qtpy import QtWidgets, QtCore
+ from qtpy.QtCore import Signal
+
try: # for matplotlib > 3.0
- from matplotlib.backends.backend_qtagg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
+ from matplotlib.backends.backend_qtagg import (
+ FigureCanvas, # ty:ignore[unresolved-import]
+ FigureManager, # ty:ignore[unresolved-import]
+ NavigationToolbar2QT as NavigationToolbar,
+ )
except ModuleNotFoundError:
- from matplotlib.backends.backend_qt5agg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
+ from matplotlib.backends.backend_qt5agg import (
+ FigureCanvas, # ty:ignore[unresolved-import]
+ FigureManager, # ty:ignore[unresolved-import]
+ NavigationToolbar2QT as NavigationToolbar,
+ )
from matplotlib.figure import Figure
class MatplotlibWidget(FigureCanvas):
quick_draw = True
+ window_pylustrator = None # "PlotLayout" | None = None
- def __init__(self, parent=None, num=1, size=None, dpi=100, figure=None, *args, **kwargs):
+ def __init__(self, parent=None, figure=None, *args, **kwargs):
if figure is None:
- self.figure = Figure(figsize=size, dpi=dpi, *args, **kwargs)
+ self.figure = Figure(*args, **kwargs)
else:
self.figure = figure
@@ -60,24 +76,27 @@ def __init__(self, parent=None, num=1, size=None, dpi=100, figure=None, *args, *
self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.updateGeometry()
-
+
self.manager = FigureManager(self, 1)
self.manager._cidgcf = self.figure
self.timer = QtCore.QTimer()
self.timer.setInterval(300)
self.timer.timeout.connect(self.draw)
- timer = None
+
+ timer: QtCore.QTimer | None = None
+
def schedule_draw(self):
if self.quick_draw is True:
return super().draw()
- if not self.timer.isActive():
+ if self.timer and not self.timer.isActive():
self.timer.start()
def draw(self):
- self.timer.stop()
- import traceback
- #print(traceback.print_stack())
+ if self.timer:
+ self.timer.stop()
+ # import traceback
+ # print(traceback.print_stack())
t = time.time()
super().draw()
duration = time.time() - t
@@ -86,7 +105,7 @@ def draw(self):
self.quick_draw = False
else:
self.quick_draw = True
-
+
def show(self):
self.draw()
@@ -117,20 +136,20 @@ def __setstate__(self, state):
class CanvasWindow(QtWidgets.QWidget):
- signal = QtCore.Signal()
+ signal = Signal()
def __init__(self, num="", *args, **kwargs):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle("Figure %s" % num)
self.setWindowIcon(qta.icon("fa5s.bar-chart"))
- self.layout = QtWidgets.QVBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
- self.layout.setSpacing(0)
+ self.layout_main = QtWidgets.QVBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
+ self.layout_main.setSpacing(0)
self.canvas = MatplotlibWidget(self, *args, **kwargs)
self.canvas.window = self
- self.layout.addWidget(self.canvas)
+ self.layout_main.addWidget(self.canvas)
self.toolbar = NavigationToolbar(self.canvas, self)
- self.layout.addWidget(self.toolbar)
+ self.layout_main.addWidget(self.toolbar)
self.signal.connect(self.show)
diff --git a/pylustrator/components/plot_layout.py b/pylustrator/components/plot_layout.py
index 316d620..d90d3ea 100644
--- a/pylustrator/components/plot_layout.py
+++ b/pylustrator/components/plot_layout.py
@@ -1,34 +1,54 @@
import os
import numpy as np
+from typing import TYPE_CHECKING
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets, _version_info
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
-if _version_info[0] == 6:
- QActionGroup = QtGui.QActionGroup
-else:
QActionGroup = QtWidgets.QActionGroup
+else:
+ from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets, _version_info
+
+ if _version_info[0] == 6:
+ QActionGroup = QtGui.QActionGroup
+ else:
+ QActionGroup = QtWidgets.QActionGroup
import matplotlib.transforms as transforms
from matplotlib.figure import Figure
+from matplotlib.backend_bases import MouseEvent
+
try: # for matplotlib > 3.0
- from matplotlib.backends.backend_qtagg import (FigureCanvas as Canvas, NavigationToolbar2QT as NavigationToolbar)
+ from matplotlib.backends.backend_qtagg import (
+ FigureCanvas as Canvas, # ty:ignore[unresolved-import]
+ NavigationToolbar2QT as NavigationToolbar,
+ )
except ModuleNotFoundError:
- from matplotlib.backends.backend_qt5agg import (FigureCanvas as Canvas, NavigationToolbar2QT as NavigationToolbar)
+ from matplotlib.backends.backend_qt5agg import (
+ FigureCanvas as Canvas, # ty:ignore[unresolved-import]
+ NavigationToolbar2QT as NavigationToolbar,
+ )
from .matplotlibwidget import MatplotlibWidget
+class GraphicsRectItemWithView(QtWidgets.QGraphicsRectItem):
+ view: QtWidgets.QGraphicsView | None = None
+ pass
+
+
class MyScene(QtWidgets.QGraphicsScene):
grabber_pressed = None
- def mousePressEvent(self, e):
+ def mousePressEvent(self, e): # ty:ignore[invalid-method-override]
super().mousePressEvent(e)
- def mouseReleaseEvent(self, e):
+ def mouseReleaseEvent(self, e): # ty:ignore[invalid-method-override]
super().mouseReleaseEvent(e)
if self.grabber_pressed:
self.grabber_pressed.mouseReleaseEvent(e)
+
class MyView(QtWidgets.QGraphicsView):
grabber_found = False
grabber_pressed = None
@@ -37,8 +57,11 @@ def __init__(self, *args):
super().__init__(*args)
self.setMouseTracking(True)
- def event(self, e):
- if e.type() == QtCore.QEvent.KeyPress or e.type() == QtCore.QEvent.KeyRelease:
+ def event(self, e: QtCore.QEvent):
+ if (
+ e.type() == QtCore.QEvent.Type.KeyPress
+ or e.type() == QtCore.QEvent.Type.KeyRelease
+ ):
self.canvas_canvas.event(e)
super().event(e)
return True
@@ -63,53 +86,41 @@ def mousePressEvent(self, e):
e.ignore()
self.canvas_canvas.mousePressEvent(e)
+
class MyEvent:
def __init__(self, x, y):
self.x = x
self.y = y
-class MyRect(QtWidgets.QGraphicsRectItem):
- w = 10
- def __init__(self, x, y, grabber):
- self.grabber = grabber
- super().__init__(x-self.w/2, y-self.w/2, self.w, self.w)
- def mousePressEvent(self, e):
- super().mousePressEvent(e)
- self.view.grabber_found = True
- p = e.scenePos()
- self.grabber.button_press_event(MyEvent(p.x(), self.h - p.y()))
-
- def mouseReleaseEvent(self, e):
- super().mouseReleaseEvent(e)
- self.view.grabber_found = True
- p = e.scenePos()
- self.grabber.button_release_event(MyEvent(p.x(), self.h - p.y()))
class Canvas(QtWidgets.QWidget):
fitted_to_view = False
footer_label = None
footer_label2 = None
- canvas = None
+ canvas: MatplotlibWidget | None = None
- def __init__(self, signals: "Signals"):
- """ The wrapper around the matplotlib canvas to create a more image editor like canvas with background and side rulers
- """
+ def __init__(self, signals):
+ """The wrapper around the matplotlib canvas to create a more image editor like canvas with background and side rulers"""
super().__init__()
signals.figure_changed.connect(self.setFigure)
signals.figure_selection_update.connect(self.updateRuler)
- signals.figure_size_changed.connect(lambda: (self.updateFigureSize(), self.updateRuler()))
+ signals.figure_size_changed.connect(
+ lambda: (self.updateFigureSize(), self.updateRuler())
+ )
self.signals = signals
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.canvas_canvas = QtWidgets.QWidget(self)
- self.layout.addWidget(self.canvas_canvas)
- self.canvas_canvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
+ self.layout_main.addWidget(self.canvas_canvas)
+ self.canvas_canvas.setSizePolicy(
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
+ )
self.canvas_canvas.setStyleSheet("background:#d1d1d1")
- self.canvas_canvas.setFocusPolicy(QtCore.Qt.StrongFocus)
+ self.canvas_canvas.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self.shadow = QtWidgets.QLabel(self.canvas_canvas)
self.canvas_border = QtWidgets.QLabel(self.canvas_canvas)
@@ -125,7 +136,7 @@ def __init__(self, signals: "Signals"):
self.y_scale = QtWidgets.QLabel(self.canvas_canvas)
self.selections_scene = MyScene()
- self.selections_scene_origin = QtWidgets.QGraphicsRectItem()
+ self.selections_scene_origin = GraphicsRectItemWithView()
self.selections_scene_origin.setTransform(QtGui.QTransform(1, 0, 0, -1, 0, 0))
self.selections_scene.addItem(self.selections_scene_origin)
@@ -133,8 +144,12 @@ def __init__(self, signals: "Signals"):
self.selections_scene_origin.view = self.selections_view
self.selections_view.parent = self
self.selections_view.setStyleSheet("background:transparent")
- self.selections_view.setAttribute(QtCore.Qt.WA_TranslucentBackground)
- self.selections_view.setAttribute(QtCore.Qt.WA_NoSystemBackground)
+ self.selections_view.setAttribute(
+ QtCore.Qt.WidgetAttribute.WA_TranslucentBackground
+ )
+ self.selections_view.setAttribute(
+ QtCore.Qt.WidgetAttribute.WA_NoSystemBackground
+ )
self.selections_view.canvas_canvas = self.canvas_canvas
def setFigure(self, figure):
@@ -143,6 +158,8 @@ def setFigure(self, figure):
del self.canvas
self.canvas = MatplotlibWidget(self, figure=figure)
+ if self.canvas is None:
+ return
self.canvas.window_pylustrator = self
self.canvas_wrapper_layout.addWidget(self.canvas)
@@ -154,14 +171,14 @@ def setFigure(self, figure):
self.fig.canvas.mpl_disconnect(self.fig.canvas.manager.key_press_handler_id)
- self.fig.canvas.mpl_connect('scroll_event', self.scroll_event)
- self.fig.canvas.mpl_connect('key_press_event', self.canvas_key_press)
- self.fig.canvas.mpl_connect('key_release_event', self.canvas_key_release)
+ self.fig.canvas.mpl_connect("scroll_event", self.scroll_event)
+ self.fig.canvas.mpl_connect("key_press_event", self.canvas_key_press)
+ self.fig.canvas.mpl_connect("key_release_event", self.canvas_key_release)
self.control_modifier = False
- self.fig.canvas.mpl_connect('button_press_event', self.button_press_event)
- self.fig.canvas.mpl_connect('motion_notify_event', self.mouse_move_event)
- self.fig.canvas.mpl_connect('button_release_event', self.button_release_event)
+ self.fig.canvas.mpl_connect("button_press_event", self.button_press_event)
+ self.fig.canvas.mpl_connect("motion_notify_event", self.mouse_move_event)
+ self.fig.canvas.mpl_connect("button_release_event", self.button_release_event)
self.drag = None
self.signals.canvas_changed.emit(self.canvas)
@@ -173,9 +190,14 @@ def setFooters(self, footer, footer2):
self.footer_label2 = footer2
def updateRuler(self):
- """ update the ruler around the figure to show the dimensions """
- trans = transforms.Affine2D().scale(1. / 2.54, 1. / 2.54) + self.fig.dpi_scale_trans
- l = 20
+ if self.canvas is None:
+ return
+ """update the ruler around the figure to show the dimensions"""
+ trans = (
+ transforms.Affine2D().scale(1.0 / 2.54, 1.0 / 2.54)
+ + self.fig.dpi_scale_trans
+ )
+ l0 = 20
l1 = 20
l2 = 10
l3 = 5
@@ -185,8 +207,12 @@ def updateRuler(self):
w = self.canvas.width()
h = self.canvas.height()
self.selections_scene.setSceneRect(0, 0, w, h)
- self.selections_view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.selections_view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.selections_view.setHorizontalScrollBarPolicy(
+ QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
+ )
+ self.selections_view.setVerticalScrollBarPolicy(
+ QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
+ )
self.selections_view.setMinimumSize(w, h)
self.selections_view.setMaximumSize(w, h)
p = self.canvas_container.pos()
@@ -199,8 +225,8 @@ def updateRuler(self):
w = self.canvas_canvas.width()
h = self.canvas_canvas.height()
- self.pixmapX = QtGui.QPixmap(w, l)
- self.pixmapY = QtGui.QPixmap(l, h)
+ self.pixmapX = QtGui.QPixmap(w, l0)
+ self.pixmapY = QtGui.QPixmap(l0, h)
self.pixmapX.fill(QtGui.QColor("#f0f0f0"))
self.pixmapY.fill(QtGui.QColor("#f0f0f0"))
@@ -221,27 +247,34 @@ def updateRuler(self):
medium_lines = big_lines / 2
dx = big_lines / 10
- positions = np.hstack([np.arange(0, start_x, -dx)[::-1], np.arange(0, end_x, dx)])
+ positions = np.hstack(
+ [np.arange(0, start_x, -dx)[::-1], np.arange(0, end_x, dx)]
+ )
for i, pos_cm in enumerate(positions):
- #for i, pos_cm in enumerate(np.arange(start_x, end_x, dx)):
- x = (trans.transform((pos_cm, 0))[0] + offset)
+ # for i, pos_cm in enumerate(np.arange(start_x, end_x, dx)):
+ x = trans.transform((pos_cm, 0))[0] + offset
if pos_cm % big_lines == 0:
- painterX.drawLine(int(x), int(l - l1 - 1), int(x), int(l - 1))
+ painterX.drawLine(int(x), int(l0 - l1 - 1), int(x), int(l0 - 1))
text = str("%d" % np.round(pos_cm))
o = 0
- painterX.drawText(int(x + 3), int(o - 3), int(self.fontMetrics().width(text)), int(o + self.fontMetrics().height()),
- QtCore.Qt.AlignLeft,
- text)
+ painterX.drawText(
+ int(x + 3),
+ int(o - 3),
+ int(self.fontMetrics().width(text)),
+ int(o + self.fontMetrics().height()),
+ QtCore.Qt.AlignLeft, # ty:ignore[unresolved-attribute]
+ text,
+ )
elif pos_cm % medium_lines == 0:
- painterX.drawLine(int(x), int(l - l2 - 1), int(x), int(l - 1))
+ painterX.drawLine(int(x), int(l0 - l2 - 1), int(x), int(l0 - 1))
else:
- painterX.drawLine(int(x), int(l - l3 - 1), int(x), int(l - 1))
- painterX.drawLine(0, l - 2, w, l - 2)
+ painterX.drawLine(int(x), int(l0 - l3 - 1), int(x), int(l0 - 1))
+ painterX.drawLine(0, l0 - 2, w, l0 - 2)
painterX.setPen(QtGui.QPen(QtGui.QColor("white"), 1))
- painterX.drawLine(0, l - 1, w, l - 1)
+ painterX.drawLine(0, l0 - 1, w, l0 - 1)
self.x_scale.setPixmap(self.pixmapX)
- self.x_scale.setMinimumSize(w, l)
- self.x_scale.setMaximumSize(w, l)
+ self.x_scale.setMinimumSize(w, l0)
+ self.x_scale.setMaximumSize(w, l0)
offset = self.canvas_container.pos().y() + self.canvas_container.height()
start_y = np.floor(trans.inverted().transform((0, +offset - h))[1])
@@ -251,35 +284,42 @@ def updateRuler(self):
big_lines = 1
medium_lines = 0.5
- pix_per_cm = trans.transform((0, 1))[1]-trans.transform((0, 0))[1]
- big_lines = int(np.ceil(self.fontMetrics().height()*5/pix_per_cm))
+ pix_per_cm = trans.transform((0, 1))[1] - trans.transform((0, 0))[1]
+ big_lines = int(np.ceil(self.fontMetrics().height() * 5 / pix_per_cm))
medium_lines = big_lines / 2
dy = big_lines / 10
- positions = np.hstack([np.arange(0, start_y, -dy)[::-1], np.arange(0, end_y, dy)])
+ positions = np.hstack(
+ [np.arange(0, start_y, -dy)[::-1], np.arange(0, end_y, dy)]
+ )
for i, pos_cm in enumerate(positions):
- y = (-trans.transform((0, pos_cm))[1] + offset)
+ y = -trans.transform((0, pos_cm))[1] + offset
if pos_cm % big_lines == 0:
- painterY.drawLine(int(l - l1 - 1), int(y), int(l - 1), int(y))
+ painterY.drawLine(int(l0 - l1 - 1), int(y), int(l0 - 1), int(y))
text = str("%d" % np.round(pos_cm))
o = 0
for ti, t in enumerate(text):
- painterY.drawText(int(o), int(y + 3 + self.fontMetrics().height()*ti),
- int(o + self.fontMetrics().width("0")), int(self.fontMetrics().height()),
- QtCore.Qt.AlignCenter, t)
+ painterY.drawText(
+ int(o),
+ int(y + 3 + self.fontMetrics().height() * ti),
+ int(o + self.fontMetrics().width("0")),
+ int(self.fontMetrics().height()),
+ QtCore.Qt.AlignmentFlag.AlignCenter,
+ t,
+ )
elif pos_cm % medium_lines == 0:
- painterY.drawLine(int(l - l2 - 1), int(y), int(l - 1), int(y))
+ painterY.drawLine(int(l0 - l2 - 1), int(y), int(l0 - 1), int(y))
else:
- painterY.drawLine(int(l - l3 - 1), int(y), int(l - 1), int(y))
- painterY.drawLine(int(l - 2), 0, int(l - 2), int(h))
+ painterY.drawLine(int(l0 - l3 - 1), int(y), int(l0 - 1), int(y))
+ painterY.drawLine(int(l0 - 2), 0, int(l0 - 2), int(h))
painterY.setPen(QtGui.QPen(QtGui.QColor("white"), 1))
- painterY.drawLine(int(l - 1), 0, int(l - 1), int(h))
+ painterY.drawLine(int(l0 - 1), 0, int(l0 - 1), int(h))
painterY.setPen(QtGui.QPen(QtGui.QColor("#f0f0f0"), 0))
painterY.setBrush(QtGui.QBrush(QtGui.QColor("#f0f0f0")))
- painterY.drawRect(0, 0, int(l), int(l))
+ painterY.drawRect(0, 0, int(l0), int(l0))
self.y_scale.setPixmap(self.pixmapY)
- self.y_scale.setMinimumSize(l, h)
- self.y_scale.setMaximumSize(l, h)
+ self.y_scale.setMinimumSize(l0, h)
+ self.y_scale.setMaximumSize(l0, h)
w, h = self.canvas.get_width_height()
@@ -304,53 +344,64 @@ def updateRuler(self):
self.canvas_border.setMaximumSize(w + 2, h + 2)
def fitToView(self, change_dpi: bool = False):
- """ fit the figure to the view """
+ """fit the figure to the view"""
+ if self.canvas is None:
+ return
self.fitted_to_view = True
if change_dpi:
w, h = self.canvas.get_width_height()
- factor = min((self.canvas_canvas.width() - 30) / w, (self.canvas_canvas.height() - 30) / h)
+ factor = min(
+ (self.canvas_canvas.width() - 30) / w,
+ (self.canvas_canvas.height() - 30) / h,
+ )
self.fig.set_dpi(self.fig.get_dpi() * factor)
- #self.fig.canvas.draw()
+ # self.fig.canvas.draw()
self.canvas.updateGeometry()
w, h = self.canvas.get_width_height()
self.canvas_container.setMinimumSize(w, h)
self.canvas_container.setMaximumSize(w, h)
- self.canvas_container.move(int((self.canvas_canvas.width() - w) / 2 + 10),
- int((self.canvas_canvas.height() - h) / 2 + 10))
+ self.canvas_container.move(
+ int((self.canvas_canvas.width() - w) / 2 + 10),
+ int((self.canvas_canvas.height() - h) / 2 + 10),
+ )
self.updateRuler()
- #self.fig.canvas.draw()
+ # self.fig.canvas.draw()
else:
w, h = self.canvas.get_width_height()
self.canvas_canvas.setMinimumWidth(w + 30)
self.canvas_canvas.setMinimumHeight(h + 30)
- self.canvas_container.move(int((self.canvas_canvas.width() - w) / 2 + 5),
- int((self.canvas_canvas.height() - h) / 2 + 5))
+ self.canvas_container.move(
+ int((self.canvas_canvas.width() - w) / 2 + 5),
+ int((self.canvas_canvas.height() - h) / 2 + 5),
+ )
self.updateRuler()
- def canvas_key_press(self, event: QtCore.QEvent):
- """ when a key in the canvas widget is pressed """
+ def canvas_key_press(self, event: QtGui.QKeyEvent):
+ """when a key in the canvas widget is pressed"""
if event.key == "control":
self.control_modifier = True
- def canvas_key_release(self, event: QtCore.QEvent):
- """ when a key in the canvas widget is released """
+ def canvas_key_release(self, event: QtGui.QKeyEvent):
+ """when a key in the canvas widget is released"""
if event.key == "control":
self.control_modifier = False
def moveCanvasCanvas(self, offset_x: float, offset_y: float):
- """ when the canvas is panned """
+ """when the canvas is panned"""
p = self.canvas_container.pos()
self.canvas_container.move(int(p.x() + offset_x), int(p.y() + offset_y))
self.updateRuler()
- def scroll_event(self, event: QtCore.QEvent):
- """ when the mouse wheel is used to zoom the figure """
+ def scroll_event(self, event: MouseEvent):
+ """when the mouse wheel is used to zoom the figure"""
+ if self.canvas is None:
+ return
if self.control_modifier:
new_dpi = self.fig.get_dpi() + 10 * event.step
# prevent zoom to be too far out
@@ -377,84 +428,89 @@ def scroll_event(self, event: QtCore.QEvent):
diff += pos_ax2 - pos_ax
self.moveCanvasCanvas(*diff)
- bb = self.fig.axes[0].get_position()
+ # bb = self.fig.axes[0].get_position()
def resizeEvent(self, event: QtCore.QEvent):
- """ when the window is resized """
+ """when the window is resized"""
if self.fitted_to_view:
self.fitToView(True)
else:
self.updateRuler()
def showEvent(self, event: QtCore.QEvent):
- """ when the window is shown """
+ """when the window is shown"""
self.fitToView(True)
self.updateRuler()
- def button_press_event(self, event: QtCore.QEvent):
- """ when a mouse button is pressed """
+ def button_press_event(self, event: MouseEvent):
+ """when a mouse button is pressed"""
if event.button == 2:
self.drag = np.array([event.x, event.y])
- def mouse_move_event(self, event: QtCore.QEvent):
- """ when the mouse is moved """
+ def mouse_move_event(self, event: MouseEvent):
+ """when the mouse is moved"""
if self.drag is not None:
pos = np.array([event.x, event.y])
offset = pos - self.drag
offset[1] = -offset[1]
self.moveCanvasCanvas(*offset)
- trans = transforms.Affine2D().scale(2.54, 2.54) + self.fig.dpi_scale_trans.inverted()
+ trans = (
+ transforms.Affine2D().scale(2.54, 2.54)
+ + self.fig.dpi_scale_trans.inverted()
+ )
pos = trans.transform((event.x, event.y))
- self.footer_label.setText("%.2f, %.2f (cm) [%d, %d]" % (pos[0], pos[1], event.x, event.y))
+ self.footer_label.setText( # ty:ignore[possibly-missing-attribute]
+ "%.2f, %.2f (cm) [%d, %d]" % (pos[0], pos[1], event.x, event.y)
+ )
if event.ydata is not None:
- self.footer_label2.setText("%.2f, %.2f" % (event.xdata, event.ydata))
+ self.footer_label2.setText("%.2f, %.2f" % (event.xdata, event.ydata)) # ty:ignore[possibly-missing-attribute]
else:
- self.footer_label2.setText("")
+ self.footer_label2.setText("") # ty:ignore[possibly-missing-attribute]
- def button_release_event(self, event: QtCore.QEvent):
- """ when the mouse button is released """
+ def button_release_event(self, event: MouseEvent):
+ """when the mouse button is released"""
if event.button == 2:
self.drag = None
- def keyPressEvent(self, event: QtCore.QEvent):
- """ when a key is pressed """
- if event.key() == QtCore.Qt.Key_Control:
+ def keyPressEvent(self, event: QtGui.QKeyEvent):
+ """when a key is pressed"""
+ if event.key() == QtCore.Qt.Key.Key_Control:
self.control_modifier = True
- if event.key() == QtCore.Qt.Key_Left:
+ if event.key() == QtCore.Qt.Key.Key_Left:
self.moveCanvasCanvas(-10, 0)
- if event.key() == QtCore.Qt.Key_Right:
+ if event.key() == QtCore.Qt.Key.Key_Right:
self.moveCanvasCanvas(10, 0)
- if event.key() == QtCore.Qt.Key_Up:
+ if event.key() == QtCore.Qt.Key.Key_Up:
self.moveCanvasCanvas(0, -10)
- if event.key() == QtCore.Qt.Key_Down:
+ if event.key() == QtCore.Qt.Key.Key_Down:
self.moveCanvasCanvas(0, 10)
- if event.key() == QtCore.Qt.Key_F:
+ if event.key() == QtCore.Qt.Key.Key_F:
self.fitToView(True)
- def keyReleaseEvent(self, event: QtCore.QEvent):
- """ when a key is released """
- if event.key() == QtCore.Qt.Key_Control:
+ def keyReleaseEvent(self, event: QtGui.QKeyEvent):
+ """when a key is released"""
+ if event.key() == QtCore.Qt.Key.Key_Control:
self.control_modifier = False
def updateFigureSize(self):
- """ update the size of the figure """
+ """update the size of the figure"""
+ if self.canvas is None:
+ return
w, h = self.canvas.get_width_height()
self.canvas_container.setMinimumSize(w, h)
self.canvas_container.setMaximumSize(w, h)
def changedFigureSize(self, size: tuple):
- """ change the size of the figure """
+ """change the size of the figure"""
self.fig.set_size_inches(np.array(size) / 2.54)
self.fig.canvas.draw()
-
class ToolBar(QtWidgets.QToolBar):
-
def __init__(self, canvas: Canvas, figure: Figure):
- """ A widget that displays a toolbar similar to the default Matplotlib toolbar (for the zoom and pan tool)
+ """A widget that displays a toolbar similar to the default Matplotlib toolbar (for the zoom and pan tool)
Args:
canvas: the canvas of the figure
@@ -467,37 +523,49 @@ def __init__(self, canvas: Canvas, figure: Figure):
self.navi_toolbar.hide()
self._actions = self.navi_toolbar._actions
- self._actions["home"] = self.addAction(self.navi_toolbar._icon("home.png"), "", self.navi_toolbar.home)
+ self._actions["home"] = self.addAction(
+ self.navi_toolbar._icon("home.png"), "", self.navi_toolbar.home
+ )
- self._actions["back"] = self.addAction(self.navi_toolbar._icon("back.png"), "", self.navi_toolbar.back)
+ self._actions["back"] = self.addAction(
+ self.navi_toolbar._icon("back.png"), "", self.navi_toolbar.back
+ )
- self._actions["forward"] = self.addAction(self.navi_toolbar._icon("forward.png"), "", self.navi_toolbar.forward)
+ self._actions["forward"] = self.addAction(
+ self.navi_toolbar._icon("forward.png"), "", self.navi_toolbar.forward
+ )
self.addSeparator()
# the action group makes the actions exclusive, you
# can't use 2 at the same time
action_group = QActionGroup(self)
- self._actions["drag"] = self.addAction(self.icon("arrow.png"), "", self.setSelect)
+ self._actions["drag"] = self.addAction(
+ self.icon("arrow.png"), "", self.setSelect
+ )
self._actions["drag"].setCheckable(True)
self._actions["drag"].setActionGroup(action_group)
- self._actions["pan"] = self.addAction(self.navi_toolbar._icon("move.png"), "", self.setPan)
+ self._actions["pan"] = self.addAction(
+ self.navi_toolbar._icon("move.png"), "", self.setPan
+ )
self._actions["pan"].setCheckable(True)
self._actions["pan"].setActionGroup(action_group)
- self._actions["zoom"] = self.addAction(self.navi_toolbar._icon("zoom_to_rect.png"), "", self.setZoom)
+ self._actions["zoom"] = self.addAction(
+ self.navi_toolbar._icon("zoom_to_rect.png"), "", self.setZoom
+ )
self._actions["zoom"].setCheckable(True)
self._actions["zoom"].setActionGroup(action_group)
- self.navi_toolbar._active = 'DRAG'
- self._actions['drag'].setChecked(True)
- self.prev_active = 'DRAG'
+ self.navi_toolbar._active = "DRAG"
+ self._actions["drag"].setChecked(True)
+ self.prev_active = "DRAG"
def icon(self, name: str):
- """ get an icon with the given filename """
- pm = QtGui.QPixmap(os.path.join(os.path.dirname(__file__), "..","icons", name))
- if hasattr(pm, 'setDevicePixelRatio'):
+ """get an icon with the given filename"""
+ pm = QtGui.QPixmap(os.path.join(os.path.dirname(__file__), "..", "icons", name))
+ if hasattr(pm, "setDevicePixelRatio"):
try: # older mpl < 3.5.0
pm.setDevicePixelRatio(self.canvas._dpi_ratio)
except AttributeError:
@@ -506,42 +574,43 @@ def icon(self, name: str):
return QtGui.QIcon(pm)
def setSelect(self):
- """ select the pylustrator selection and drag tool """
+ """select the pylustrator selection and drag tool"""
self.fig.figure_dragger.activate()
- if self.prev_active=="PAN":
+ if self.prev_active == "PAN":
self.navi_toolbar.pan()
- elif self.prev_active=="ZOOM":
+ elif self.prev_active == "ZOOM":
self.navi_toolbar.zoom()
- self.prev_active = 'DRAG'
+ self.prev_active = "DRAG"
- self.navi_toolbar._active = 'DRAG'
+ self.navi_toolbar._active = "DRAG"
def setPan(self):
- """ select the mpl pan tool """
+ """select the mpl pan tool"""
if self.prev_active == "DRAG":
self.fig.figure_dragger.deactivate()
- if self.navi_toolbar._active != 'PAN':
+ if self.navi_toolbar._active != "PAN":
self.navi_toolbar.pan()
- self.prev_active = 'PAN'
+ self.prev_active = "PAN"
def setZoom(self):
- """ select the mpl zoom tool """
+ """select the mpl zoom tool"""
if self.prev_active == "DRAG":
self.fig.figure_dragger.deactivate()
- if self.navi_toolbar._active != 'ZOOM':
+ if self.navi_toolbar._active != "ZOOM":
self.navi_toolbar.zoom()
- self.prev_active = 'ZOOM'
+ self.prev_active = "ZOOM"
+
class PlotLayout(QtWidgets.QWidget):
toolbar = None
- def __init__(self, signals: "Signals"):
+ def __init__(self, signals):
super().__init__()
self.setMinimumSize(600, 500)
@@ -552,7 +621,9 @@ def __init__(self, signals: "Signals"):
self.layout_plot.setContentsMargins(0, 0, 0, 0)
self.layout_plot.setSpacing(0)
- self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
+ self.setSizePolicy(
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred
+ )
self.canvas_canvas = Canvas(signals)
self.layout_plot.addWidget(self.canvas_canvas)
diff --git a/pylustrator/components/qitem_properties.py b/pylustrator/components/qitem_properties.py
index bf5bf8b..abbfdc0 100644
--- a/pylustrator/components/qitem_properties.py
+++ b/pylustrator/components/qitem_properties.py
@@ -20,147 +20,184 @@
# along with Pylustrator. If not, see
import os
-from typing import Any
+from typing import TYPE_CHECKING, Any, List, Optional, Tuple, cast, Literal
import numpy as np
from packaging import version
import qtawesome as qta
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-try: # starting from mpl version 3.6.0
- from matplotlib.axes import Axes
-except:
- from matplotlib.axes._subplots import Axes
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
+ from PyQt5.QtCore import pyqtSignal as Signal
+else:
+ from qtpy import QtCore, QtGui, QtWidgets
+ from qtpy.QtCore import Signal
+from PyQt5.QtCore import pyqtBoundSignal
+
+from matplotlib.axes import Axes
import matplotlib as mpl
-import matplotlib.pyplot as plt
from matplotlib.artist import Artist
from matplotlib.figure import Figure
from matplotlib.ticker import AutoLocator
+from matplotlib.patches import Rectangle, FancyArrowPatch
+from matplotlib.legend import Legend
+from matplotlib.text import Text
from pylustrator.change_tracker import getReference
-from pylustrator.QLinkableWidgets import QColorWidget, CheckWidget, TextWidget, DimensionsWidget, NumberWidget, ComboWidget
+from pylustrator.QLinkableWidgets import (
+ QColorWidget,
+ CheckWidget,
+ TextWidget,
+ DimensionsWidget,
+ NumberWidget,
+ ComboWidget,
+)
from pylustrator.helper_functions import main_figure
from pylustrator.change_tracker import UndoRedo, add_text_default, add_axes_default
class TextPropertiesWidget(QtWidgets.QWidget):
- stateChanged = QtCore.Signal(int, str)
- noSignal = False
- target_list = None
+ stateChanged = Signal(int, str)
+ noSignal: bool = False
+ target_list: List[Artist]
+ target: Artist | None
def __init__(self, layout: QtWidgets.QLayout):
- """ A widget to edit the properties of a Matplotlib text
+ """A widget to edit the properties of a Matplotlib text
Args:
layout: the layout to which to add the widget
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.buttons_align = []
self.align_names = ["left", "center", "right"]
align_group = QtWidgets.QButtonGroup(self)
for align in self.align_names:
button = QtWidgets.QPushButton(qta.icon("fa5s.align-" + align), "")
- button.setToolTip("align "+align)
+ button.setToolTip("align " + align)
button.setCheckable(True)
button.clicked.connect(lambda x, name=align: self.changeAlign(name))
- self.layout.addWidget(button)
+ self.layout_main.addWidget(button)
self.buttons_align.append(button)
align_group.addButton(button)
self.button_bold = QtWidgets.QPushButton(qta.icon("fa5s.bold"), "")
self.button_bold.setCheckable(True)
self.button_bold.clicked.connect(self.changeWeight)
- self.layout.addWidget(self.button_bold)
+ self.layout_main.addWidget(self.button_bold)
self.button_italic = QtWidgets.QPushButton(qta.icon("fa5s.italic"), "")
self.button_italic.setCheckable(True)
self.button_italic.clicked.connect(self.changeStyle)
- self.layout.addWidget(self.button_italic)
+ self.layout_main.addWidget(self.button_italic)
- self.button_color = QColorWidget(self.layout)
+ self.button_color = QColorWidget(self.layout_main)
self.button_color.valueChanged.connect(self.changeColor)
- self.layout.addStretch()
+ self.layout_main.addStretch()
self.font_size = QtWidgets.QSpinBox()
- self.layout.addWidget(self.font_size)
+ self.layout_main.addWidget(self.font_size)
self.font_size.valueChanged.connect(self.changeFontSize)
self.label = QtWidgets.QPushButton(qta.icon("fa5s.font"), "") # .pixmap(16))
- self.layout.addWidget(self.label)
+ self.layout_main.addWidget(self.label)
self.label.clicked.connect(self.selectFont)
- #self.button_delete = QtWidgets.QPushButton(qta.icon("fa5s.trash"), "")
- #self.button_delete.clicked.connect(self.delete)
- #self.button_delete.setToolTip("delete")
- #self.layout.addWidget(self.button_delete)
+ # self.button_delete = QtWidgets.QPushButton(qta.icon("fa5s.trash"), "")
+ # self.button_delete.clicked.connect(self.delete)
+ # self.button_delete.setToolTip("delete")
+ # self.layout.addWidget(self.button_delete)
def convertMplWeightToQtWeight(self, weight: str) -> int:
- """ convert a font weight string to a weight enumeration of Qt """
- weight_dict = {'normal': QtGui.QFont.Normal, 'bold': QtGui.QFont.Bold, 'heavy': QtGui.QFont.ExtraBold,
- 'light': QtGui.QFont.Light, 'ultrabold': QtGui.QFont.Black, 'ultralight': QtGui.QFont.ExtraLight}
+ """convert a font weight string to a weight enumeration of Qt"""
+ weight_dict = {
+ "normal": QtGui.QFont.Normal,
+ "bold": QtGui.QFont.Bold,
+ "heavy": QtGui.QFont.ExtraBold,
+ "light": QtGui.QFont.Light,
+ "ultrabold": QtGui.QFont.Black,
+ "ultralight": QtGui.QFont.ExtraLight,
+ }
if weight in weight_dict:
return weight_dict[weight]
return weight_dict["normal"]
def convertQtWeightToMplWeight(self, weight: int) -> str:
- """ convert a Qt weight value to a string for use in matmplotlib """
- weight_dict = {QtGui.QFont.Normal: 'normal', QtGui.QFont.Bold: 'bold', QtGui.QFont.ExtraBold: 'heavy',
- QtGui.QFont.Light: 'light', QtGui.QFont.Black: 'ultrabold', QtGui.QFont.ExtraLight: 'ultralight'}
+ """convert a Qt weight value to a string for use in matmplotlib"""
+ weight_dict = {
+ QtGui.QFont.Normal: "normal",
+ QtGui.QFont.Bold: "bold",
+ QtGui.QFont.ExtraBold: "heavy",
+ QtGui.QFont.Light: "light",
+ QtGui.QFont.Black: "ultrabold",
+ QtGui.QFont.ExtraLight: "ultralight",
+ }
if weight in weight_dict:
return weight_dict[weight]
return "normal"
def selectFont(self):
- """ open a font select dialog """
+ """open a font select dialog"""
+ if self.target is None or not isinstance(self.target, Text):
+ return
font0 = QtGui.QFont()
font0.setFamily(self.target.get_fontname())
- font0.setWeight(self.convertMplWeightToQtWeight(self.target.get_weight()))
- font0.setItalic(self.target.get_style() == "italic")
+ font0.setWeight(
+ self.convertMplWeightToQtWeight(str(self.target.get_fontweight()))
+ )
+ font0.setItalic(self.target.get_fontstyle() == "italic")
font0.setPointSizeF(int(self.target.get_fontsize()))
font, x = QtWidgets.QFontDialog.getFont(font0, self)
with UndoRedo(self.target_list, "Change font size"):
for element in self.target_list:
- element.set_fontname(font.family())
- if font.weight() != font0.weight():
- element.set_weight(self.convertQtWeightToMplWeight(font.weight()))
- if font.pointSizeF() != font0.pointSizeF():
- element.set_fontsize(font.pointSizeF())
- if font.italic() != font0.italic():
- element.set_style("italic" if font.italic() else "normal")
+ if isinstance(element, Text):
+ element.set_fontname(font.family())
+ if font.weight() != font0.weight():
+ element.set_fontweight(
+ self.convertQtWeightToMplWeight(font.weight())
+ )
+ if font.pointSizeF() != font0.pointSizeF():
+ element.set_fontsize(font.pointSizeF())
+ if font.italic() != font0.italic():
+ element.set_fontstyle("italic" if font.italic() else "normal")
self.setTarget(self.target_list)
- def setTarget(self, element: Artist):
- """ set the target artist for this widget """
+ def setTarget(self, element: Artist | List[Artist] | None):
+ """set the target artist for this widget"""
if isinstance(element, list):
- self.target_list = element
- element = element[0]
+ if len(element) == 0:
+ return
+ self.target_list = cast(List[Artist], element)
+ text_element = cast(Artist, element[0])
+ elif element is None:
+ self.target_list = []
+ return
else:
- if element is None:
- self.target_list = []
- else:
- self.target_list = [element]
+ self.target_list = [element]
+ text_element = element
self.target = None
- self.font_size.setValue(int(element.get_fontsize()))
- index_selected = self.align_names.index(element.get_ha())
- for index, button in enumerate(self.buttons_align):
- button.setChecked(index == index_selected)
+ if isinstance(text_element, Text):
+ self.font_size.setValue(int(text_element.get_fontsize()))
+ index_selected = self.align_names.index(text_element.get_horizontalalignment())
+ for index, button in enumerate(self.buttons_align):
+ button.setChecked(index == index_selected)
- self.button_bold.setChecked(element.get_weight() == "bold")
- self.button_italic.setChecked(element.get_style() == "italic")
- self.button_color.setColor(element.get_color())
+ self.button_bold.setChecked(text_element.get_fontweight() == "bold")
+ self.button_italic.setChecked(text_element.get_fontstyle() == "italic")
+ self.button_color.setColor(text_element.get_color())
- self.target = element
+ self.target = text_element
def delete(self):
- """ delete the target text """
+ """delete the target text"""
if self.target is not None:
fig = main_figure(self.target)
fig.change_tracker.removeElement(self.target)
@@ -169,57 +206,62 @@ def delete(self):
fig.canvas.draw()
def changeWeight(self, checked: bool):
- """ set bold or normal """
+ """set bold or normal"""
if self.target:
with UndoRedo(self.target_list, "Change weight"):
for element in self.target_list:
- element.set_weight("bold" if checked else "normal")
+ if isinstance(element, Text):
+ element.set_fontweight("bold" if checked else "normal")
def changeStyle(self, checked: bool):
- """ set italic or normal """
+ """set italic or normal"""
if self.target:
with UndoRedo(self.target_list, "Change style"):
for element in self.target_list:
- element.set_style("italic" if checked else "normal")
+ if isinstance(element, Text):
+ element.set_fontstyle("italic" if checked else "normal")
def changeColor(self, color: str):
- """ set the text color """
+ """set the text color"""
if self.target:
with UndoRedo(self.target_list, "Change color"):
for element in self.target_list:
- element.set_color(color)
+ if isinstance(element, Text):
+ element.set_color(color)
- def changeAlign(self, align: str):
- """ set the text algin """
+ def changeAlign(self, align: Literal["left", "center", "right"]):
+ """set the text algin"""
if self.target:
with UndoRedo(self.target_list, "Change alignment"):
for element in self.target_list:
- element.set_ha(align)
+ if isinstance(element, Text):
+ element.set_horizontalalignment(align)
def changeFontSize(self, value: int):
- """ set the font size """
+ """set the font size"""
if self.target:
with UndoRedo(self.target_list, "Change font size"):
for element in self.target_list:
- element.set_fontsize(value)
+ if isinstance(element, Text):
+ element.set_fontsize(value)
class TextPropertiesWidget2(QtWidgets.QWidget):
- stateChanged = QtCore.Signal(int, str)
- propertiesChanged = QtCore.Signal()
+ stateChanged = Signal(int, str)
+ propertiesChanged = Signal()
noSignal = False
target_list = None
def __init__(self, layout: QtWidgets.QLayout):
- """ A widget to edit the properties of a Matplotlib text
+ """A widget to edit the properties of a Matplotlib text
Args:
layout: the layout to which to add the widget
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.buttons_align = []
self.align_names = ["left", "center", "right"]
@@ -229,30 +271,30 @@ def __init__(self, layout: QtWidgets.QLayout):
button.setCheckable(True)
button.clicked.connect(lambda x, name=align: self.changeAlign(name))
align_group.addButton(button)
- self.layout.addWidget(button)
+ self.layout_main.addWidget(button)
self.buttons_align.append(button)
self.button_bold = QtWidgets.QPushButton(qta.icon("fa5s.bold"), "")
self.button_bold.setCheckable(True)
self.button_bold.clicked.connect(self.changeWeight)
- self.layout.addWidget(self.button_bold)
+ self.layout_main.addWidget(self.button_bold)
self.button_italic = QtWidgets.QPushButton(qta.icon("fa5s.italic"), "")
self.button_italic.setCheckable(True)
self.button_italic.clicked.connect(self.changeStyle)
- self.layout.addWidget(self.button_italic)
+ self.layout_main.addWidget(self.button_italic)
- self.button_color = QColorWidget(self.layout)
+ self.button_color = QColorWidget(self.layout_main)
self.button_color.valueChanged.connect(self.changeColor)
- self.layout.addStretch()
+ self.layout_main.addStretch()
self.font_size = QtWidgets.QSpinBox()
- self.layout.addWidget(self.font_size)
+ self.layout_main.addWidget(self.font_size)
self.font_size.valueChanged.connect(self.changeFontSize)
self.label = QtWidgets.QPushButton(qta.icon("fa5s.font"), "") # .pixmap(16))
- self.layout.addWidget(self.label)
+ self.layout_main.addWidget(self.label)
self.label.clicked.connect(self.selectFont)
self.property_names = [
@@ -265,36 +307,57 @@ def __init__(self, layout: QtWidgets.QLayout):
]
self.properties = {}
- self.propertiesChanged.connect(lambda: self.target and main_figure(self.target).signals.figure_selection_property_changed.emit())
+ self.propertiesChanged.connect(
+ lambda: self.target
+ and main_figure(
+ self.target
+ ).signals.figure_selection_property_changed.emit()
+ )
def convertMplWeightToQtWeight(self, weight: str) -> int:
- """ convert a font weight string to a weight enumeration of Qt """
- weight_dict = {'normal': QtGui.QFont.Normal, 'bold': QtGui.QFont.Bold, 'heavy': QtGui.QFont.ExtraBold,
- 'light': QtGui.QFont.Light, 'ultrabold': QtGui.QFont.Black, 'ultralight': QtGui.QFont.ExtraLight}
+ """convert a font weight string to a weight enumeration of Qt"""
+ weight_dict = {
+ "normal": QtGui.QFont.Normal,
+ "bold": QtGui.QFont.Bold,
+ "heavy": QtGui.QFont.ExtraBold,
+ "light": QtGui.QFont.Light,
+ "ultrabold": QtGui.QFont.Black,
+ "ultralight": QtGui.QFont.ExtraLight,
+ }
if weight in weight_dict:
return weight_dict[weight]
return weight_dict["normal"]
def convertQtWeightToMplWeight(self, weight: int) -> str:
- """ convert a Qt weight value to a string for use in matmplotlib """
- weight_dict = {QtGui.QFont.Normal: 'normal', QtGui.QFont.Bold: 'bold', QtGui.QFont.ExtraBold: 'heavy',
- QtGui.QFont.Light: 'light', QtGui.QFont.Black: 'ultrabold', QtGui.QFont.ExtraLight: 'ultralight'}
+ """convert a Qt weight value to a string for use in matmplotlib"""
+ weight_dict = {
+ QtGui.QFont.Normal: "normal",
+ QtGui.QFont.Bold: "bold",
+ QtGui.QFont.ExtraBold: "heavy",
+ QtGui.QFont.Light: "light",
+ QtGui.QFont.Black: "ultrabold",
+ QtGui.QFont.ExtraLight: "ultralight",
+ }
if weight in weight_dict:
return weight_dict[weight]
return "normal"
def selectFont(self):
- """ open a font select dialog """
+ """open a font select dialog"""
font0 = QtGui.QFont()
font0.setFamily(self.target.get_fontname())
- font0.setWeight(self.convertMplWeightToQtWeight(self.target.get_weight()))
- font0.setItalic(self.target.get_style() == "italic")
+ font0.setWeight(
+ self.convertMplWeightToQtWeight(str(self.target.get_fontweight()))
+ )
+ font0.setItalic(self.target.get_fontstyle() == "italic")
font0.setPointSizeF(int(self.target.get_fontsize()))
font, x = QtWidgets.QFontDialog.getFont(font0, self)
self.properties["fontname"] = font.family()
if font.weight() != font0.weight():
- self.properties["fontweight"] = self.convertQtWeightToMplWeight(font.weight())
+ self.properties["fontweight"] = self.convertQtWeightToMplWeight(
+ font.weight()
+ )
if font.pointSizeF() != font0.pointSizeF():
self.properties["fontsize"] = font.pointSizeF()
if font.italic() != font0.italic():
@@ -302,43 +365,48 @@ def selectFont(self):
self.properties["fontstyle"] = style
self.propertiesChanged.emit()
- #main_figure(self.target).canvas.draw()
- self.setTarget(self.target_list)
+ # main_figure(self.target).canvas.draw()
+ if self.target_list is not None:
+ self.setTarget(self.target_list)
- def setTarget(self, element: Artist):
- """ set the target artist for this widget """
+ def setTarget(self, element: Artist | List[Artist]):
+ """set the target artist for this widget"""
self.noSignal = True
try:
- if len(element) == 0:
- return
if isinstance(element, list):
+ if len(element) == 0:
+ return
self.target_list = element
- element = element[0]
+ current_element = element[0]
else:
if element is None:
self.target_list = []
+ current_element = None
else:
self.target_list = [element]
+ current_element = element
self.target = None
- self.font_size.setValue(int(element.get_fontsize()))
- index_selected = self.align_names.index(element.get_ha())
+ if not isinstance(current_element, Text):
+ return
+ self.font_size.setValue(int(current_element.get_fontsize()))
+ index_selected = self.align_names.index(current_element.get_horizontalalignment())
for index, button in enumerate(self.buttons_align):
button.setChecked(index == index_selected)
- self.button_bold.setChecked(element.get_weight() == "bold")
- self.button_italic.setChecked(element.get_style() == "italic")
- self.button_color.setColor(element.get_color())
+ self.button_bold.setChecked(current_element.get_fontweight() == "bold")
+ self.button_italic.setChecked(current_element.get_fontstyle() == "italic")
+ self.button_color.setColor(current_element.get_color())
for name, name2, type_, default_ in self.property_names:
- value = getattr(element, "get_"+name2)()
+ value = getattr(current_element, "get_" + name2)()
self.properties[name] = value
- self.target = element
+ self.target = current_element
finally:
self.noSignal = False
def delete(self):
- """ delete the target text """
+ """delete the target text"""
if self.target is not None:
fig = main_figure(self.target)
fig.change_tracker.removeElement(self.target)
@@ -347,27 +415,27 @@ def delete(self):
fig.canvas.draw()
def changeWeight(self, checked: bool):
- """ set bold or normal """
+ """set bold or normal"""
self.properties["fontweight"] = "bold" if checked else "normal"
self.propertiesChanged.emit()
def changeStyle(self, checked: bool):
- """ set italic or normal """
+ """set italic or normal"""
self.properties["fontstyle"] = "italic" if checked else "normal"
self.propertiesChanged.emit()
def changeColor(self, color: str):
- """ set the text color """
+ """set the text color"""
self.properties["color"] = color
self.propertiesChanged.emit()
def changeAlign(self, align: str):
- """ set the text algin """
+ """set the text algin"""
self.properties["horizontalalignment"] = align
self.propertiesChanged.emit()
def changeFontSize(self, value: int):
- """ set the font size """
+ """set the font size"""
if self.noSignal:
return
self.properties["fontsize"] = value
@@ -375,82 +443,131 @@ def changeFontSize(self, value: int):
class LegendPropertiesWidget(QtWidgets.QWidget):
- stateChanged = QtCore.Signal(int, str)
+ stateChanged = Signal(int, str)
noSignal = False
target_list = None
+ target: Artist | None = None
def __init__(self, layout: QtWidgets.QLayout):
- """ A widget that allows to change to properties of a matplotlib legend
+ """A widget that allows to change to properties of a matplotlib legend
Args:
layout: the layout to which to add the widget
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QVBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QVBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
- from packaging import version
- import matplotlib as mpl
ncols_name = "ncols"
- if version.parse(mpl._get_version()) < version.parse("3.6.0"):
+ if version.parse(mpl.__version__) < version.parse("3.6.0"):
ncols_name = "ncol"
self.property_names = [
("frameon", "frameon", bool, None, None),
("borderpad", "borderpad", float, None, "legend_icon_borderpad.png"),
- ("labelspacing", "labelspacing", float, None, "legend_icon_labelspacing.png"),
+ (
+ "labelspacing",
+ "labelspacing",
+ float,
+ None,
+ "legend_icon_labelspacing.png",
+ ),
("markerscale", "markerscale", float, None, "legend_icon_markerscale.png"),
- ("handlelength", "handlelength", float, None, "legend_icon_handlelength.png"),
- ("handletextpad", "handletextpad", float, None, "legend_icon_handletextpad.png"),
+ (
+ "handlelength",
+ "handlelength",
+ float,
+ None,
+ "legend_icon_handlelength.png",
+ ),
+ (
+ "handletextpad",
+ "handletextpad",
+ float,
+ None,
+ "legend_icon_handletextpad.png",
+ ),
(ncols_name, "_" + ncols_name, int, 1, None),
- ("columnspacing", "columnspacing", float, None, "legend_icon_columnspacing.png"),
+ (
+ "columnspacing",
+ "columnspacing",
+ float,
+ None,
+ "legend_icon_columnspacing.png",
+ ),
("fontsize", "_fontsize", int, None, "legend_icon_fontsize.png"),
("title", "title", str, "", None),
- ("title_fontsize", "title_fontsize", int, None, "legend_icon_title_fontsize.png"),
+ (
+ "title_fontsize",
+ "title_fontsize",
+ int,
+ None,
+ "legend_icon_title_fontsize.png",
+ ),
]
self.properties = {}
self.widgets = {}
- for index, (name, name2, type_, default_, icon) in enumerate(self.property_names):
+ for index, (name, name2, type_, default_, icon) in enumerate(
+ self.property_names
+ ):
if index % 3 == 0:
layout = QtWidgets.QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
- self.layout.addLayout(layout)
- if type_ == bool:
+ self.layout_main.addLayout(layout)
+ if type_ is bool:
widget = CheckWidget(layout, name + ":")
widget.editingFinished.connect(
- lambda name=name, widget=widget: self.changePropertiy(name, widget.get()))
- elif type_ == str:
+ lambda name=name, widget=widget: self.changePropertiy(
+ name, widget.get()
+ )
+ )
+ elif type_ is str:
widget = TextWidget(layout, name + ":")
widget.editingFinished.connect(
- lambda name=name, widget=widget: self.changePropertiy(name, widget.get()))
+ lambda name=name, widget=widget: self.changePropertiy(
+ name, widget.get()
+ )
+ )
else:
label = QtWidgets.QLabel(name + ":")
layout.addWidget(label)
- if type_ == float:
+ if type_ is float:
widget = QtWidgets.QDoubleSpinBox()
widget.setSingleStep(0.1)
- elif type_ == int:
+ elif type_ is int:
widget = QtWidgets.QSpinBox()
if name == ncols_name:
widget.setMinimum(1)
widget.label = label
layout.addWidget(widget)
- widget.valueChanged.connect(lambda x, name=name: self.changePropertiy(name, x))
+ widget.valueChanged.connect(
+ lambda x, name=name: self.changePropertiy(name, x)
+ )
if icon is not None and getattr(widget, "label", None):
from pathlib import Path
+
+ dpi = 96
+ screen = QtGui.QGuiApplication.primaryScreen()
+ if screen is not None:
+ dpi = screen.logicalDotsPerInch()
pix = QtGui.QPixmap(str(Path(__file__).parent.parent / "icons" / icon))
- pix = pix.scaledToWidth(int(28*QtGui.QGuiApplication.primaryScreen().logicalDotsPerInch()/96), QtCore.Qt.SmoothTransformation)
+ pix = pix.scaledToWidth(
+ int(28 * dpi / 96),
+ QtCore.Qt.TransformationMode.SmoothTransformation,
+ )
widget.setToolTip(name)
widget.label.setToolTip(name)
widget.label.setPixmap(pix)
- widget.label.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+ widget.label.setSizePolicy(
+ QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed
+ )
self.widgets[name] = widget
def changePropertiy(self, name: str, value: Any):
- """ change the property with the given name to the provided value """
+ """change the property with the given name to the provided value"""
if self.target is None:
return
@@ -458,16 +575,29 @@ def changePropertiy(self, name: str, value: Any):
self.properties[name] = value
new_properties = self.properties.copy()
target = self.target
+
def setProperties(properties):
nonlocal target
+ if not isinstance(target, Legend):
+ return
bbox = target.get_frame().get_bbox()
axes = target.axes
+ if not isinstance(axes, Axes):
+ return
axes.legend(**properties)
target = axes.get_legend()
+ if not isinstance(target, Legend):
+ return
fig = main_figure(target)
- target._set_loc(tuple(target.axes.transAxes.inverted().transform(tuple([bbox.x0, bbox.y0]))))
+ target._set_loc( # ty:ignore[unresolved-attribute]
+ tuple(
+ target.axes.transAxes.inverted().transform(
+ tuple([bbox.x0, bbox.y0])
+ )
+ )
+ )
fig.change_tracker.addNewLegendChange(target)
- fig.figure_dragger.make_dragable(target)
+ fig.figure_dragger.make_draggable(target)
fig.figure_dragger.select_element(target)
fig.canvas.draw()
fig.selection.update_selection_rectangles()
@@ -481,29 +611,32 @@ def redo():
redo()
main_figure(target).change_tracker.addEdit([undo, redo, f"Legend {name}"])
- def setTarget(self, element: Artist):
- """ set the target artist for this widget """
+ def setTarget(self, element: Artist | List[Artist] | None):
+ """set the target artist for this widget"""
if isinstance(element, list):
self.target_list = element
- element = element[0]
+ current_element = element[0]
+ elif element is None:
+ self.target_list = []
+ current_element = None
else:
- if element is None:
- self.target_list = []
- else:
- self.target_list = [element]
+ current_element = element
+ self.target_list = [current_element]
self.target = None
+ if not isinstance(current_element, Legend):
+ return
for name, name2, type_, default_, icon in self.property_names:
if name2 == "frameon":
- value = element.get_frame_on()
+ value = current_element.get_frame_on()
elif name2 == "title":
- value = element.get_title().get_text()
+ value = current_element.get_title().get_text()
elif name2 == "title_fontsize":
- value = int(element.get_title().get_fontsize())
+ value = int(current_element.get_title().get_fontsize())
elif name2 == "_fontsize":
# matplotlib fontsizes are float, but Qt requires int
- value = int(getattr(element, name2))
+ value = int(getattr(current_element, name2))
else:
- value = getattr(element, name2)
+ value = getattr(current_element, name2)
try:
self.widgets[name].setValue(value)
@@ -511,12 +644,12 @@ def setTarget(self, element: Artist):
self.widgets[name].set(value)
self.properties[name] = value
- self.target = element
+ self.target = current_element
class QTickEdit(QtWidgets.QWidget):
- def __init__(self, axis: str, signal_target_changed: QtCore.Signal):
- """ A widget to change the tick properties
+ def __init__(self, axis: str, signal_target_changed: pyqtBoundSignal):
+ """A widget to change the tick properties
Args:
axis: whether to use the "x" or "y" axis
@@ -524,42 +657,60 @@ def __init__(self, axis: str, signal_target_changed: QtCore.Signal):
"""
QtWidgets.QWidget.__init__(self)
self.setWindowTitle("Figure - " + axis + "-Axis - Ticks - Pylustrator")
- self.setWindowIcon(QtGui.QIcon(os.path.join(os.path.dirname(__file__), "../icons", "ticks.ico")))
- self.layout = QtWidgets.QVBoxLayout(self)
+ self.setWindowIcon(
+ QtGui.QIcon(
+ os.path.join(os.path.dirname(__file__), "../icons", "ticks.ico")
+ )
+ )
+ self.layout_main = QtWidgets.QVBoxLayout(self)
self.axis = axis
self.label = QtWidgets.QLabel(
- "Ticks can be specified, one tick pre line.\nOptionally a label can be provided, e.g. 1 \"First\",")
- self.layout.addWidget(self.label)
+ 'Ticks can be specified, one tick pre line.\nOptionally a label can be provided, e.g. 1 "First",'
+ )
+ self.layout_main.addWidget(self.label)
self.layout2 = QtWidgets.QHBoxLayout()
- self.layout.addLayout(self.layout2)
+ self.layout_main.addLayout(self.layout2)
- self.input_ticks = TextWidget(self.layout2, axis + "-Ticks:", multiline=True, horizontal=False)
+ self.input_ticks = TextWidget(
+ self.layout2, axis + "-Ticks:", multiline=True, horizontal=False
+ )
self.input_ticks.editingFinished.connect(self.ticksChanged)
- self.input_ticks2 = TextWidget(self.layout2, axis + "-Ticks (minor):", multiline=True, horizontal=False)
+ self.input_ticks2 = TextWidget(
+ self.layout2, axis + "-Ticks (minor):", multiline=True, horizontal=False
+ )
self.input_ticks2.editingFinished.connect(self.ticksChanged2)
- self.input_scale = ComboWidget(self.layout, axis + "-Scale", ["linear", "log", "symlog", "logit"])
+ self.input_scale = ComboWidget(
+ self.layout_main, axis + "-Scale", ["linear", "log", "symlog", "logit"]
+ )
self.input_scale.editingFinished.connect(self.scaleChanged)
- #self.input_scale.link(axis + "scale", signal_target_changed)
+ # self.input_scale.link(axis + "scale", signal_target_changed)
- self.input_font = TextPropertiesWidget2(self.layout)
+ self.input_font = TextPropertiesWidget2(self.layout_main)
self.input_font.propertiesChanged.connect(self.fontStateChanged)
- self.input_labelpad = NumberWidget(self.layout, axis + "-Labelpad", min=-999)
- self.input_labelpad.link(axis + "axis.labelpad", signal_target_changed, direct=True)
+ self.input_labelpad = NumberWidget(
+ self.layout_main, axis + "-Labelpad", min=-999
+ )
+ self.input_labelpad.link(
+ axis + "axis.labelpad", signal_target_changed, direct=True
+ )
self.button_ok = QtWidgets.QPushButton("Ok")
- self.layout.addWidget(self.button_ok)
+ self.layout_main.addWidget(self.button_ok)
self.button_ok.clicked.connect(self.hide)
- def parseTickLabel(self, line: str) -> (float, str):
- """ interpret the tick value specified in line """
+ def parseTickLabel(self, line: str) -> Tuple[float, str]:
+ """interpret the tick value specified in line"""
import re
+
line = line.replace("−", "-")
- match = re.match(r"\$\\mathdefault{(([-.\d]*)\\times)?([-.\d]+)\^{([-.\d]+)}}\$", line)
+ match = re.match(
+ r"\$\\mathdefault{(([-.\d]*)\\times)?([-.\d]+)\^{([-.\d]+)}}\$", line
+ )
if match:
_, factor, base, exponent = match.groups()
if factor is not None:
@@ -581,12 +732,17 @@ def parseTickLabel(self, line: str) -> (float, str):
number = np.nan
return number, line
- def formatTickLabel(self, line: str) -> (float, str):
- """ interpret the tick label specified in line"""
+ def formatTickLabel(self, line: str) -> Tuple[float, str]:
+ """interpret the tick label specified in line"""
import re
+
line = line.replace("−", "-")
- match = re.match(r"\s*(([-.\d]*)\s*x)?\s*([-.\d]+)\s*\^\s*([-.\d]+)\s*\"(.+)?\"", line)
- match2 = re.match(r"\s*(([-.\d]*)\s*x)?\s*([-.\d]+)\s*\^\s*([-.\d]+)\s*(.+)?", line)
+ match = re.match(
+ r"\s*(([-.\d]*)\s*x)?\s*([-.\d]+)\s*\^\s*([-.\d]+)\s*\"(.+)?\"", line
+ )
+ match2 = re.match(
+ r"\s*(([-.\d]*)\s*x)?\s*([-.\d]+)\s*\^\s*([-.\d]+)\s*(.+)?", line
+ )
if match:
_, factor, base, exponent, label = match.groups()
if factor is not None:
@@ -615,7 +771,7 @@ def formatTickLabel(self, line: str) -> (float, str):
return number, line
def setTarget(self, element: Artist):
- """ set the target Artist for this widget"""
+ """set the target Artist for this widget"""
self.element = element
self.fig = main_figure(element)
min, max = getattr(self.element, "get_" + self.axis + "lim")()
@@ -626,15 +782,15 @@ def setTarget(self, element: Artist):
ticks = getattr(self.element, "get_" + self.axis + "ticks")()
labels = getattr(self.element, "get_" + self.axis + "ticklabels")()
text = []
- for t, l in zip(ticks, labels):
- l, l_text = self.parseTickLabel(l.get_text())
+ for tick, label in zip(ticks, labels):
+ label, l_text = self.parseTickLabel(label.get_text())
try:
- l = float(l)
+ label = float(label)
except ValueError:
continue
- if min <= t <= max:
- if l != t:
- text.append("%s \"%s\"" % (str(t), l_text))
+ if min <= tick <= max:
+ if label != tick:
+ text.append('%s "%s"' % (str(tick), l_text))
else:
text.append("%s" % l_text)
self.input_ticks.setText(",
".join(text))
@@ -642,30 +798,38 @@ def setTarget(self, element: Artist):
ticks = getattr(self.element, "get_" + self.axis + "ticks")(minor=True)
labels = getattr(self.element, "get_" + self.axis + "ticklabels")(minor=True)
text = []
- for t, l in zip(ticks, labels):
- l, l_text = self.parseTickLabel(l.get_text())
+ for tick, label in zip(ticks, labels):
+ label, l_text = self.parseTickLabel(label.get_text())
try:
- l = float(l)
+ label = float(label)
except ValueError:
pass
- if min <= t <= max:
- if l != t:
- text.append("%s \"%s\"" % (str(t), l_text))
+ if min <= tick <= max:
+ if label != tick:
+ text.append('%s "%s"' % (str(tick), l_text))
else:
text.append("%s" % l_text)
self.input_ticks2.setText(",
".join(text))
elements = [self.element]
- elements += [element.target for element in main_figure(self.element).selection.targets if
- element.target != self.element and isinstance(element.target, Axes)]
+ elements += [
+ element.target
+ for element in main_figure(self.element).selection.targets
+ if element.target != self.element and isinstance(element.target, Axes)
+ ]
ticks = []
for element in elements:
- ticks += [t.label1 for t in getattr(element, "get_" + self.axis + "axis")().get_major_ticks()]
+ ticks += [
+ t.label1
+ for t in getattr(
+ element, "get_" + self.axis + "axis"
+ )().get_major_ticks()
+ ]
self.input_font.setTarget(ticks)
def parseTicks(self, string: str):
- """ parse a list of given ticks """
+ """parse a list of given ticks"""
try:
ticks = []
labels = []
@@ -676,39 +840,49 @@ def parseTicks(self, string: str):
tick, _ = self.formatTickLabel(line)
if np.isnan(tick) and len(two_parts) == 2:
tick = float(two_parts[0].replace("−", "-"))
- label = two_parts[1].strip("\"")
+ label = two_parts[1].strip('"')
else:
tick, label = self.formatTickLabel(line)
- except ValueError as err:
+ except ValueError:
pass
else:
if not np.isnan(tick):
ticks.append(tick)
labels.append(label)
- except Exception as err:
+ except Exception:
pass
return ticks, labels
def str(self, object: Any):
- """ serialize an object and interpret nan values """
+ """serialize an object and interpret nan values"""
if str(object) == "nan":
return "np.nan"
return str(object)
def ticksChanged2(self):
- """ when the minor ticks changed """
+ """when the minor ticks changed"""
ticks, labels = self.parseTicks(self.input_ticks2.text())
elements = [self.element]
- elements += [element.target for element in main_figure(self.element).selection.targets if
- element.target != self.element and isinstance(element.target, Axes)]
+ elements += [
+ element.target
+ for element in main_figure(self.element).selection.targets
+ if element.target != self.element and isinstance(element.target, Axes)
+ ]
changed = False
for elem in elements:
current_ticks = getattr(elem, "get_" + self.axis + "ticks")(minor=True)
- current_ticklabels = [t.get_text() for t in getattr(elem, "get_" + self.axis + "ticklabels")(minor=True)]
- if len(current_ticks) != len(ticks) or (current_ticks != ticks).any() or \
- len(current_ticklabels) != len(labels) or current_ticklabels != labels:
+ current_ticklabels = [
+ t.get_text()
+ for t in getattr(elem, "get_" + self.axis + "ticklabels")(minor=True)
+ ]
+ if (
+ len(current_ticks) != len(ticks)
+ or (current_ticks != ticks).any()
+ or len(current_ticklabels) != len(labels)
+ or current_ticklabels != labels
+ ):
changed = True
if changed is False:
return
@@ -726,33 +900,46 @@ def ticksChanged2(self):
getattr(element, "set_" + self.axis + "ticklabels")(labels, minor=True)
min, max = getattr(element, "get_" + self.axis + "lim")()
if min != self.range[0] or max != self.range[1]:
- self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "lim(%s, %s)" % (str(min), str(max)))
+ self.fig.change_tracker.addChange(
+ element, ".set_" + self.axis + "lim(%s, %s)" % (str(min), str(max))
+ )
else:
- self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "lim(%s, %s)" % (
- str(self.range[0]), str(self.range[1])))
+ self.fig.change_tracker.addChange(
+ element,
+ ".set_"
+ + self.axis
+ + "lim(%s, %s)" % (str(self.range[0]), str(self.range[1])),
+ )
# self.setTarget(element)
- self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "ticks([%s], [%s], minor=True)" % (
- ", ".join(self.str(t) for t in ticks),
- ", ".join('"' + l + '"' for l in labels)),
- element, ".set_" + self.axis + "ticks_minor")
+ self.fig.change_tracker.addChange(
+ element,
+ ".set_"
+ + self.axis
+ + "ticks([%s], [%s], minor=True)"
+ % (
+ ", ".join(self.str(t) for t in ticks),
+ ", ".join('"' + label + '"' for label in labels),
+ ),
+ element,
+ ".set_" + self.axis + "ticks_minor",
+ )
self.fig.canvas.draw()
def getFontProperties(self):
prop_copy = {}
prop_copy2 = {}
- for index, (name, name2, type_, default_) in enumerate(self.input_font.property_names):
+ for index, (name, name2, type_, default_) in enumerate(
+ self.input_font.property_names
+ ):
if name not in self.input_font.properties:
continue
value = self.input_font.properties[name]
if default_ is not None and value == default_:
continue
- #if default_ is None and value == plt.rcParams["legend." + name]:
+ # if default_ is None and value == plt.rcParams["legend." + name]:
# continue
- if type_ == str:
+ if type_ is str:
prop_copy[name] = '"' + value + '"'
else:
prop_copy[name] = value
@@ -761,13 +948,16 @@ def getFontProperties(self):
def fontStateChanged(self):
self.ticksChanged()
- #fig.change_tracker.addChange(axes, ".legend(%s)" % (", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())))
+ # fig.change_tracker.addChange(axes, ".legend(%s)" % (", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())))
def scaleChanged(self):
- """ when the scale changed """
+ """when the scale changed"""
elements = [self.element]
- elements += [element.target for element in main_figure(self.element).selection.targets if
- element.target != self.element and isinstance(element.target, Axes)]
+ elements += [
+ element.target
+ for element in main_figure(self.element).selection.targets
+ if element.target != self.element and isinstance(element.target, Axes)
+ ]
with UndoRedo(elements, "Axes Scale"):
for element in elements:
@@ -776,19 +966,28 @@ def scaleChanged(self):
element.set(**kwargs)
def ticksChanged(self):
- """ when the major ticks changed """
+ """when the major ticks changed"""
ticks, labels = self.parseTicks(self.input_ticks.text())
elements = [self.element]
- elements += [element.target for element in main_figure(self.element).selection.targets if
- element.target != self.element and isinstance(element.target, Axes)]
+ elements += [
+ element.target
+ for element in main_figure(self.element).selection.targets
+ if element.target != self.element and isinstance(element.target, Axes)
+ ]
changed = False
for elem in elements:
current_ticks = getattr(elem, "get_" + self.axis + "ticks")()
- current_ticklabels = [t.get_text() for t in getattr(elem, "get_" + self.axis + "ticklabels")()]
- if len(current_ticks) != len(ticks) or (current_ticks != ticks).any() or \
- len(current_ticklabels) != len(labels) or current_ticklabels != labels:
+ current_ticklabels = [
+ t.get_text() for t in getattr(elem, "get_" + self.axis + "ticklabels")()
+ ]
+ if (
+ len(current_ticks) != len(ticks)
+ or (current_ticks != ticks).any()
+ or len(current_ticklabels) != len(labels)
+ or current_ticklabels != labels
+ ):
changed = True
if changed is False:
return
@@ -805,7 +1004,9 @@ def ticksChanged(self):
if 0:
getattr(element, "set_" + self.axis + "lim")(self.range)
getattr(element, "set_" + self.axis + "ticks")(ticks)
- getattr(element, "set_" + self.axis + "ticklabels")(labels, **self.getFontProperties()[1])
+ getattr(element, "set_" + self.axis + "ticklabels")(
+ labels, **self.getFontProperties()[1]
+ )
changed = False
for elem in elements:
@@ -823,11 +1024,16 @@ def ticksChanged(self):
formatter = axis.major.formatter
lim = getattr(element, "get_" + self.axis + "lim")()
ticks2 = getattr(element, "get_" + self.axis + "ticks")()
- ticklabels = [t.get_text() for t in getattr(element, "get_" + self.axis + "ticklabels")()]
+ ticklabels = [
+ t.get_text()
+ for t in getattr(element, "get_" + self.axis + "ticklabels")()
+ ]
old_properties.append([locator, formatter, lim, ticks2, ticklabels])
def undo():
- for element, (locator, formator, lim, ticks, labels) in zip(elements, old_properties):
+ for element, (locator, formator, lim, ticks, labels) in zip(
+ elements, old_properties
+ ):
axis = getattr(element, "get_" + self.axis + "axis")()
axis.set_major_locator(locator)
axis.set_major_formatter(formator)
@@ -836,31 +1042,59 @@ def undo():
# make sure there are no old changes to this element
keys = [k for k in fig.change_tracker.changes]
for reference_obj, reference_command in keys:
- if reference_obj == element and reference_command == ".set_" + self.axis + "ticks":
- del fig.change_tracker.changes[reference_obj, reference_command]
+ if (
+ reference_obj == element
+ and reference_command == ".set_" + self.axis + "ticks"
+ ):
+ del fig.change_tracker.changes[
+ reference_obj, reference_command
+ ]
else:
- self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "ticks([%s], [%s], %s)" % (", ".join(
- self.str(t) for t in ticks), ", ".join(
- '"' + l + '"' for l in labels), self.getFontProperties()[0]))
+ self.fig.change_tracker.addChange(
+ element,
+ ".set_"
+ + self.axis
+ + "ticks([%s], [%s], %s)"
+ % (
+ ", ".join(self.str(t) for t in ticks),
+ ", ".join('"' + label + '"' for label in labels),
+ self.getFontProperties()[0],
+ ),
+ )
def redo():
for element in elements:
getattr(element, "set_" + self.axis + "lim")(self.range)
getattr(element, "set_" + self.axis + "ticks")(ticks)
- getattr(element, "set_" + self.axis + "ticklabels")(labels, **self.getFontProperties()[1])
+ getattr(element, "set_" + self.axis + "ticklabels")(
+ labels, **self.getFontProperties()[1]
+ )
min, max = getattr(element, "get_" + self.axis + "lim")()
if min != self.range[0] or max != self.range[1]:
- self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "lim(%s, %s)" % (str(min), str(max)))
+ self.fig.change_tracker.addChange(
+ element,
+ ".set_" + self.axis + "lim(%s, %s)" % (str(min), str(max)),
+ )
else:
- self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "lim(%s, %s)" % (
- str(self.range[0]), str(self.range[1])))
+ self.fig.change_tracker.addChange(
+ element,
+ ".set_"
+ + self.axis
+ + "lim(%s, %s)" % (str(self.range[0]), str(self.range[1])),
+ )
# self.setTarget(self.element)
- self.fig.change_tracker.addChange(element, ".set_" + self.axis + "ticks([%s], [%s], %s)" % (", ".join(
- self.str(t) for t in ticks), ", ".join('"' + l + '"' for l in labels), self.getFontProperties()[0]))
+ self.fig.change_tracker.addChange(
+ element,
+ ".set_"
+ + self.axis
+ + "ticks([%s], [%s], %s)"
+ % (
+ ", ".join(self.str(t) for t in ticks),
+ ", ".join('"' + label + '"' for label in labels),
+ self.getFontProperties()[0],
+ ),
+ )
self.fig.change_tracker.addEdit([undo, redo, "ticks"])
redo()
@@ -868,10 +1102,15 @@ def redo():
class QAxesProperties(QtWidgets.QWidget):
- targetChanged_wrapped = QtCore.Signal(object)
+ targetChanged_wrapped = Signal(object)
- def __init__(self, layout: QtWidgets.QLayout, axis: str, signal_target_changed: QtCore.Signal):
- """ a widget to change the properties of an axes (label, limits)
+ def __init__(
+ self,
+ layout: QtWidgets.QLayout,
+ axis: str,
+ signal_target_changed: pyqtBoundSignal,
+ ):
+ """a widget to change the properties of an axes (label, limits)
Args:
layout: the layout to which to add this widget
@@ -880,130 +1119,73 @@ def __init__(self, layout: QtWidgets.QLayout, axis: str, signal_target_changed:
"""
QtWidgets.QWidget.__init__(self)
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.targetChanged = signal_target_changed
self.targetChanged.connect(self.setTarget)
- self.input_label = TextWidget(self.layout, axis + "-Label:")
-
- def wrapTargetLabel(axis_object):
- try:
- target = getattr(getattr(axis_object, f"get_{axis}axis")(), "get_label")()
- except AttributeError:
- target = None
- self.targetChanged_wrapped.emit(target)
-
- self.targetChanged.connect(wrapTargetLabel)
- self.input_label.link("text", signal=self.targetChanged_wrapped)
+ self.input_label = TextWidget(self.layout_main, axis + "-Label:")
+ if 0:
- self.input_lim = DimensionsWidget(self.layout, axis + "-Lim:", "-", "", free=True)
+ def wrapTargetLabel(axis_object):
+ try:
+ target = getattr(
+ getattr(axis_object, f"get_{axis}axis")(), "get_label"
+ )()
+ except AttributeError:
+ target = None
+ self.targetChanged_wrapped.emit(target)
+
+ self.targetChanged.connect(wrapTargetLabel)
+ self.input_label.link("text", signal=self.targetChanged_wrapped)
+ self.input_label.link(axis + "label", signal=self.targetChanged)
+
+ self.input_lim = DimensionsWidget(
+ self.layout_main, axis + "-Lim:", "-", "", free=True
+ )
self.input_lim.link(axis + "lim", signal=self.targetChanged)
if axis == "x":
self.button_ticks = QtWidgets.QPushButton(
- QtGui.QIcon(os.path.join(os.path.dirname(__file__), "../icons", "ticks.ico")), "")
- else:
- self.button_ticks = QtWidgets.QPushButton(
- QtGui.QIcon(os.path.join(os.path.dirname(__file__), "../icons", "ticks_y.ico")), "")
- self.button_ticks.clicked.connect(self.showTickWidget)
- self.layout.addWidget(self.button_ticks)
-
- self.tick_edit = QTickEdit(axis, signal_target_changed)
-
- def showTickWidget(self):
- """ open the tick edit dialog """
- self.tick_edit.setTarget(self.element)
- self.tick_edit.show()
-
- def setTarget(self, element: Artist):
- """ set the target Artist of this widget """
- self.element = element
-
- if isinstance(element, Axes):
- self.show()
- else:
- self.hide()
-
-
-class QAxesProperties(QtWidgets.QWidget):
- targetChanged_wrapped = QtCore.Signal(object)
-
- def __init__(self, layout: QtWidgets.QLayout, axis: str, signal_target_changed: QtCore.Signal):
- """ a widget to change the properties of an axes (label, limits)
-
- Args:
- layout: the layout to which to add this widget
- axis: whether to use "x" or the "y" axis
- signal_target_changed: the signal when a target changed
- """
- QtWidgets.QWidget.__init__(self)
- layout.addWidget(self)
- self.axis = axis
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
-
- self.targetChanged = signal_target_changed
- self.targetChanged.connect(self.setTarget)
-
- self.input_label = TextWidget(self.layout, axis + "-Label:")
- self.input_label.editingFinished.connect(self.saveLabel)
- self.input_lim = DimensionsWidget(self.layout, axis + "-Lim:", "-", "", free=True)
- self.input_lim.editingFinished.connect(self.saveLim)
- if axis == "x":
- self.button_ticks = QtWidgets.QPushButton(
- QtGui.QIcon(os.path.join(os.path.dirname(__file__), "../icons", "ticks.ico")), "")
+ QtGui.QIcon(
+ os.path.join(os.path.dirname(__file__), "../icons", "ticks.ico")
+ ),
+ "",
+ )
else:
self.button_ticks = QtWidgets.QPushButton(
- QtGui.QIcon(os.path.join(os.path.dirname(__file__), "../icons", "ticks_y.ico")), "")
+ QtGui.QIcon(
+ os.path.join(os.path.dirname(__file__), "../icons", "ticks_y.ico")
+ ),
+ "",
+ )
self.button_ticks.clicked.connect(self.showTickWidget)
- self.layout.addWidget(self.button_ticks)
+ self.layout_main.addWidget(self.button_ticks)
self.tick_edit = QTickEdit(axis, signal_target_changed)
def showTickWidget(self):
- """ open the tick edit dialog """
+ """open the tick edit dialog"""
self.tick_edit.setTarget(self.element)
self.tick_edit.show()
def setTarget(self, element: Artist):
- """ set the target Artist of this widget """
+ """set the target Artist of this widget"""
self.element = element
if isinstance(element, Axes):
- self.input_label.setText(getattr(element, f"get_{self.axis}label")())
- self.input_lim.setValue(getattr(element, f"get_{self.axis}lim")())
self.show()
else:
self.hide()
- def saveLabel(self):
- elements = [self.element]
- elements += [element.target for element in main_figure(self.element).selection.targets if
- element.target != self.element and isinstance(element.target, Axes)]
-
- text = self.input_label.text()
- with UndoRedo(elements, f"Set axes {self.axis}-label"):
- for element in elements:
- element.set(**{f"{self.axis}label": text})
-
- def saveLim(self):
- elements = [self.element]
- elements += [element.target for element in main_figure(self.element).selection.targets if
- element.target != self.element and isinstance(element.target, Axes)]
-
- limits = self.input_lim.value()
- with UndoRedo(elements, f"Set axes {self.axis}-lim"):
- for element in elements:
- element.set(**{f"{self.axis}lim": limits})
class QItemProperties(QtWidgets.QWidget):
- targetChanged = QtCore.Signal(object)
- valueChanged = QtCore.Signal(tuple)
+ targetChanged = Signal(object)
+ valueChanged = Signal(tuple)
element = None
- def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
- """ a widget that holds all the properties to set and the tree view
+ def __init__(self, layout: QtWidgets.QLayout, signals):
+ """a widget that holds all the properties to set and the tree view
Args:
layout: the layout to which to add the widget
@@ -1018,67 +1200,87 @@ def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
signals.figure_changed.connect(self.setFigure)
signals.figure_element_selected.connect(self.select_element)
- self.layout = QtWidgets.QVBoxLayout(self)
- self.layout.setContentsMargins(0, 0, 0, 0)
+ self.layout_main = QtWidgets.QVBoxLayout(self)
+ self.layout_main.setContentsMargins(0, 0, 0, 0)
self.label = QtWidgets.QLabel()
- self.layout.addWidget(self.label)
+ self.layout_main.addWidget(self.label)
- self.input_text = TextWidget(self.layout, "Text:")
+ self.input_text = TextWidget(self.layout_main, "Text:")
self.input_text.link("text", self.targetChanged)
- self.input_rotation = NumberWidget(self.layout, "Rotation:")
+ self.input_rotation = NumberWidget(self.layout_main, "Rotation:")
self.input_rotation.link("rotation", self.targetChanged)
self.input_rotation.input1.setRange(-360, 360)
- self.input_xaxis = QAxesProperties(self.layout, "x", self.targetChanged)
- self.input_yaxis = QAxesProperties(self.layout, "y", self.targetChanged)
+ self.input_xaxis = QAxesProperties(self.layout_main, "x", self.targetChanged)
+ self.input_yaxis = QAxesProperties(self.layout_main, "y", self.targetChanged)
- self.input_font_properties = TextPropertiesWidget(self.layout)
+ self.input_font_properties = TextPropertiesWidget(self.layout_main)
- self.input_legend_properties = LegendPropertiesWidget(self.layout)
+ self.input_legend_properties = LegendPropertiesWidget(self.layout_main)
- self.input_label = TextWidget(self.layout, "Label:")
+ self.input_label = TextWidget(self.layout_main, "Label:")
self.input_label.link("label", self.targetChanged)
layout = QtWidgets.QHBoxLayout()
- self.layout.addLayout(layout)
+ self.layout_main.addLayout(layout)
- condition_line = lambda x: getattr(x, "get_linestyle")() not in ["None", " ", ""]
- condition_marker = lambda x: getattr(x, "get_marker")() not in ["None", " ", ""]
+ def condition_line(x):
+ return getattr(x, "get_linestyle")() not in [
+ "None",
+ " ",
+ "",
+ ]
- TextWidget(layout, "Linestyle:", allow_literal_decoding=True).link("linestyle", self.targetChanged)
+ def condition_marker(x):
+ return getattr(x, "get_marker")() not in ["None", " ", ""]
- NumberWidget(layout, "Linewidth:").link("linewidth", self.targetChanged,
- condition=condition_line) # lambda x: getattr(x, "get_linestyle") not in ["None", " ", ""])
+ TextWidget(layout, "Linestyle:", allow_literal_decoding=True).link(
+ "linestyle", self.targetChanged
+ )
- QColorWidget(layout, "Color:").link("color", self.targetChanged, condition=condition_line)
+ NumberWidget(layout, "Linewidth:").link(
+ "linewidth", self.targetChanged, condition=condition_line
+ ) # lambda x: getattr(x, "get_linestyle") not in ["None", " ", ""])
+
+ QColorWidget(layout, "Color:").link(
+ "color", self.targetChanged, condition=condition_line
+ )
layout = QtWidgets.QHBoxLayout()
- self.layout.addLayout(layout)
+ self.layout_main.addLayout(layout)
TextWidget(layout, "Markerstyle:").link("marker", self.targetChanged)
- NumberWidget(layout, "Markersize:").link("markersize", self.targetChanged, condition=condition_marker)
+ NumberWidget(layout, "Markersize:").link(
+ "markersize", self.targetChanged, condition=condition_marker
+ )
- QColorWidget(layout, "markerfacecolor:").link("markerfacecolor", self.targetChanged, condition=condition_marker)
+ QColorWidget(layout, "markerfacecolor:").link(
+ "markerfacecolor", self.targetChanged, condition=condition_marker
+ )
layout = QtWidgets.QHBoxLayout()
- self.layout.addLayout(layout)
+ self.layout_main.addLayout(layout)
- NumberWidget(layout, "Markeredgewidth:").link("markeredgewidth", self.targetChanged, condition=condition_marker)
+ NumberWidget(layout, "Markeredgewidth:").link(
+ "markeredgewidth", self.targetChanged, condition=condition_marker
+ )
- QColorWidget(layout, "markeredgecolor:").link("markeredgecolor", self.targetChanged, condition=condition_marker)
+ QColorWidget(layout, "markeredgecolor:").link(
+ "markeredgecolor", self.targetChanged, condition=condition_marker
+ )
layout = QtWidgets.QHBoxLayout()
- self.layout.addLayout(layout)
+ self.layout_main.addLayout(layout)
QColorWidget(layout, "Edgecolor:").link("edgecolor", self.targetChanged)
QColorWidget(layout, "Facecolor:").link("facecolor", self.targetChanged)
self.layout_buttons = QtWidgets.QHBoxLayout()
- self.layout.addLayout(self.layout_buttons)
+ self.layout_main.addLayout(self.layout_buttons)
self.button_add_image = QtWidgets.QPushButton("add image")
self.layout_buttons.addWidget(self.button_add_image)
@@ -1092,7 +1294,7 @@ def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
self.layout_buttons.addWidget(self.button_add_annotation)
self.button_add_annotation.clicked.connect(self.buttonAddAnnotationClicked)
self.layout_buttons = QtWidgets.QHBoxLayout()
- self.layout.addLayout(self.layout_buttons)
+ self.layout_main.addLayout(self.layout_buttons)
self.button_add_rectangle = QtWidgets.QPushButton("add rectangle")
self.layout_buttons.addWidget(self.button_add_rectangle)
@@ -1103,7 +1305,7 @@ def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
self.button_add_arrow.clicked.connect(self.buttonAddArrowClicked)
self.layout_buttons = QtWidgets.QHBoxLayout()
- self.layout.addLayout(self.layout_buttons)
+ self.layout_main.addLayout(self.layout_buttons)
self.button_despine = QtWidgets.QPushButton("despine")
self.layout_buttons.addWidget(self.button_despine)
@@ -1121,7 +1323,7 @@ def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
self.setMinimumWidth(100)
def select_element(self, element: Artist):
- """ select an element """
+ """select an element"""
if element is None:
self.setElement(self.fig)
else:
@@ -1131,15 +1333,16 @@ def setFigure(self, fig):
self.fig = fig
def buttonAddImageClicked(self):
- """ when the button 'add image' is clicked """
+ """when the button 'add image' is clicked"""
fig = self.fig
def addChange(element, command):
fig.change_tracker.addChange(element, command)
return eval(getReference(element) + command)
- path = QtWidgets.QFileDialog.getOpenFileName(self, "Open Image", os.getcwd(),
- "Image *.jpg *.png *.tif")
+ path = QtWidgets.QFileDialog.getOpenFileName(
+ self, "Open Image", os.getcwd(), "Image *.jpg *.png *.tif"
+ )
if isinstance(path, tuple):
path = str(path[0])
else:
@@ -1150,13 +1353,17 @@ def addChange(element, command):
if isinstance(self.element, Figure):
axes = self.element.add_axes([0.25, 0.25, 0.5, 0.5], label=filename)
fig.ax_dict = {ax.get_label(): ax for ax in fig.axes}
- self.fig.change_tracker.addChange(self.element,
- ".add_axes([0.25, 0.25, 0.5, 0.5], label=\"%s\") # id=%s.new" % (
- filename, getReference(axes)), axes, ".new")
+ self.fig.change_tracker.addChange(
+ self.element,
+ '.add_axes([0.25, 0.25, 0.5, 0.5], label="%s") # id=%s.new'
+ % (filename, getReference(axes)),
+ axes,
+ ".new",
+ )
add_axes_default(axes)
- addChange(axes, ".imshow(plt.imread(\"%s\"))" % filename)
- addChange(axes, '.set_xticks([])')
- addChange(axes, '.set_yticks([])')
+ addChange(axes, '.imshow(plt.imread("%s"))' % filename)
+ addChange(axes, ".set_xticks([])")
+ addChange(axes, ".set_yticks([])")
if 0:
addChange(axes, ".spines['right'].set_visible(False)")
addChange(axes, ".spines['left'].set_visible(False)")
@@ -1166,7 +1373,7 @@ def addChange(element, command):
addChange(axes, ".spines[:].set_visible(False)")
self.signals.figure_element_child_created.emit(self.element)
- self.fig.figure_dragger.make_dragable(axes)
+ self.fig.figure_dragger.make_draggable(axes)
self.fig.figure_dragger.select_element(axes)
self.fig.canvas.draw()
self.setElement(axes)
@@ -1174,21 +1381,25 @@ def addChange(element, command):
self.input_text.input1.setFocus()
def buttonAddTextClicked(self):
- """ when the button 'add text' is clicked """
+ """when the button 'add text' is clicked"""
if isinstance(self.element, Axes):
- text = self.element.text(0.5, 0.5, "New Text", transform=self.element.transAxes)
+ text = self.element.text(
+ 0.5, 0.5, "New Text", transform=self.element.transAxes
+ )
text.is_new_text = True
add_text_default(text)
self.fig.change_tracker.addNewTextChange(text)
if isinstance(self.element, Figure):
- text = self.element.text(0.5, 0.5, "New Text", transform=self.element.transFigure)
+ text = self.element.text(
+ 0.5, 0.5, "New Text", transform=self.element.transFigure
+ )
text.is_new_text = True
add_text_default(text)
self.fig.change_tracker.addNewTextChange(text)
self.signals.figure_element_child_created.emit(self.element)
- self.fig.figure_dragger.make_dragable(text)
+ self.fig.figure_dragger.make_draggable(text)
self.fig.canvas.draw()
self.fig.figure_dragger.on_deselect(None)
self.fig.figure_dragger.selection.clear_targets()
@@ -1199,17 +1410,29 @@ def buttonAddTextClicked(self):
self.input_text.input1.setFocus()
def buttonAddAnnotationClicked(self):
- """ when the button 'add annoations' is clicked """
- text = self.element.annotate("New Annotation", (self.element.get_xlim()[0], self.element.get_ylim()[0]),
- (np.mean(self.element.get_xlim()), np.mean(self.element.get_ylim())),
- arrowprops=dict(arrowstyle="->"))
- self.fig.change_tracker.addChange(self.element,
- ".annotate('New Annotation', %s, %s, arrowprops=dict(arrowstyle='->')) # id=%s.new" % (
- text.xy, text.get_position(), getReference(text)),
- text, ".new")
+ """when the button 'add annoations' is clicked"""
+ if not isinstance(self.element, Axes):
+ return
+ xlim = self.element.get_xlim()
+ ylim = self.element.get_ylim()
+ x = float(np.mean(xlim))
+ y = float(np.mean(ylim))
+ text = self.element.annotate(
+ "New Annotation",
+ (xlim[0], ylim[0]),
+ (x, y),
+ arrowprops=dict(arrowstyle="->"),
+ )
+ self.fig.change_tracker.addChange(
+ self.element,
+ ".annotate('New Annotation', %s, %s, arrowprops=dict(arrowstyle='->')) # id=%s.new"
+ % (text.xy, text.get_position(), getReference(text)),
+ text,
+ ".new",
+ )
self.signals.figure_element_child_created.emit(self.element)
- self.fig.figure_dragger.make_dragable(text)
+ self.fig.figure_dragger.make_draggable(text)
self.fig.figure_dragger.select_element(text)
self.fig.canvas.draw()
self.setElement(text)
@@ -1217,18 +1440,28 @@ def buttonAddAnnotationClicked(self):
self.input_text.input1.setFocus()
def buttonAddRectangleClicked(self):
- """ when the button 'add rectangle' is clicked """
- p = mpl.patches.Rectangle((self.element.get_xlim()[0], self.element.get_ylim()[0]),
- width=np.mean(self.element.get_xlim()), height=np.mean(self.element.get_ylim()), )
+ """when the button 'add rectangle' is clicked"""
+ if not isinstance(self.element, Axes):
+ return
+ y_min, y_max = self.element.get_ylim()
+ x_min, x_max = self.element.get_xlim()
+ p = Rectangle(
+ (x_min, y_min),
+ width=(x_max - x_min) / 2,
+ height=(y_max - y_min) / 2,
+ )
self.element.add_patch(p)
- self.fig.change_tracker.addChange(self.element,
- ".add_patch(mpl.patches.Rectangle(%s, width=%s, height=%s)) # id=%s.new" % (
- p.get_xy(), p.get_width(), p.get_height(), getReference(p)),
- p, ".new")
+ self.fig.change_tracker.addChange(
+ self.element,
+ ".add_patch(mpl.patches.Rectangle(%s, width=%s, height=%s)) # id=%s.new"
+ % (p.get_xy(), p.get_width(), p.get_height(), getReference(p)),
+ p,
+ ".new",
+ )
self.signals.figure_element_child_created.emit(self.element)
- self.fig.figure_dragger.make_dragable(p)
+ self.fig.figure_dragger.make_draggable(p)
self.fig.figure_dragger.select_element(p)
self.fig.canvas.draw()
self.setElement(p)
@@ -1236,20 +1469,37 @@ def buttonAddRectangleClicked(self):
self.input_text.input1.setFocus()
def buttonAddArrowClicked(self):
- """ when the button 'add arrow' is clicked """
- p = mpl.patches.FancyArrowPatch((self.element.get_xlim()[0], self.element.get_ylim()[0]),
- (np.mean(self.element.get_xlim()), np.mean(self.element.get_ylim())),
- arrowstyle="Simple,head_length=10,head_width=10,tail_width=2",
- facecolor="black", clip_on=False, zorder=2)
+ """when the button 'add arrow' is clicked"""
+ if not isinstance(self.element, Axes):
+ return
+ posA = (
+ float(self.element.get_xlim()[0]),
+ float(self.element.get_ylim()[0]),
+ )
+ posB = (
+ float(np.mean(self.element.get_xlim())),
+ float(np.mean(self.element.get_ylim())),
+ )
+ p = FancyArrowPatch(
+ posA,
+ posB,
+ arrowstyle="Simple,head_length=10,head_width=10,tail_width=2",
+ facecolor="black",
+ clip_on=False,
+ zorder=2,
+ )
self.element.add_patch(p)
- self.fig.change_tracker.addChange(self.element,
- ".add_patch(mpl.patches.FancyArrowPatch(%s, %s, arrowstyle='Simple,head_length=10,head_width=10,tail_width=2', facecolor='black', clip_on=False, zorder=2)) # id=%s.new" % (
- p._posA_posB[0], p._posA_posB[1], getReference(p)),
- p, ".new")
+ self.fig.change_tracker.addChange(
+ self.element,
+ ".add_patch(mpl.patches.FancyArrowPatch(%s, %s, arrowstyle='Simple,head_length=10,head_width=10,tail_width=2', facecolor='black', clip_on=False, zorder=2)) # id=%s.new"
+ % (posA, posB, getReference(p)),
+ p,
+ ".new",
+ )
self.signals.figure_element_child_created.emit(self.element)
- self.fig.figure_dragger.make_dragable(p)
+ self.fig.figure_dragger.make_draggable(p)
self.fig.figure_dragger.select_element(p)
self.fig.canvas.draw()
self.setElement(p)
@@ -1257,14 +1507,22 @@ def buttonAddArrowClicked(self):
self.input_text.input1.setFocus()
def buttonDespineClicked(self):
- """ despine the target """
+ """despine the target"""
+
+ elements = [
+ element.target
+ for element in main_figure(self.element).selection.targets
+ if isinstance(element.target, Axes)
+ ]
- elements = [element.target for element in main_figure(self.element).selection.targets if isinstance(element.target, Axes)]
def is_despined(elem):
- return elem.spines['right'].get_visible() and elem.spines['top'].get_visible()
- despined = [is_despined(elem) for elem in elements]
+ return (
+ elem.spines["right"].get_visible() and elem.spines["top"].get_visible()
+ )
+
+ # despined = [is_despined(elem) for elem in elements]
new_value = not is_despined(self.element)
- fig = main_figure(self.element)
+ main_figure(self.element)
with UndoRedo(elements, "Despine"):
for element in elements:
@@ -1272,25 +1530,42 @@ def is_despined(elem):
element.spines[spine].set_visible(new_value)
def buttonGridClicked(self):
- """ toggle the grid of the target """
- elements = [element.target for element in main_figure(self.element).selection.targets
- if isinstance(element.target, Axes)]
- has_grid = getattr(self.element.xaxis, "_gridOnMajor", False) or getattr(self.element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']
+ """toggle the grid of the target"""
+ if not isinstance(self.element, Axes):
+ return
+ elements = [
+ element.target
+ for element in main_figure(self.element).selection.targets
+ if isinstance(element.target, Axes)
+ ]
+ has_grid = (
+ getattr(self.element.xaxis, "_gridOnMajor", False)
+ or getattr(self.element.xaxis, "_major_tick_kw", {"gridOn": False})[
+ "gridOn"
+ ]
+ )
with UndoRedo(elements, "Toggle Grid"):
for element in elements:
element.grid(not has_grid)
if 0:
+
def set_false():
for element in elements:
element.grid(False)
self.fig.change_tracker.addChange(element, ".grid(False)")
+
def set_true():
for element in elements:
element.grid(True)
self.fig.change_tracker.addChange(element, ".grid(True)")
- if getattr(self.element.xaxis, "_gridOnMajor", False) or getattr(self.element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']:
+ if (
+ getattr(self.element.xaxis, "_gridOnMajor", False)
+ or getattr(self.element.xaxis, "_major_tick_kw", {"gridOn": False})[
+ "gridOn"
+ ]
+ ):
set_false()
self.fig.change_tracker.addEdit([set_true, set_false, "Grid off"])
else:
@@ -1299,29 +1574,30 @@ def set_true():
self.fig.canvas.draw()
def buttonLegendClicked(self):
- """ add a legend to the target """
+ """add a legend to the target"""
+ if not isinstance(self.element, Axes):
+ return
self.element.legend()
self.fig.change_tracker.addChange(self.element, ".legend()")
- self.fig.figure_dragger.make_dragable(self.element.get_legend())
+ self.fig.figure_dragger.make_draggable(self.element.get_legend())
self.fig.canvas.draw()
self.signals.figure_element_child_created.emit(self.element)
def changePickable(self):
- """ make the target pickable """
+ """make the target pickable"""
+ draggable = getattr(self.element, "_draggable", None)
+ if draggable is None:
+ return
if self.input_picker.isChecked():
- self.element._draggable.connect()
+ draggable.connect()
else:
- self.element._draggable.disconnect()
+ draggable.disconnect()
self.signals.figure_element_child_created.emit(self.element)
- def setElement(self, element: Artist):
- """ set the target Artist of this widget """
+ def setElement(self, element: Artist | None):
+ """set the target Artist of this widget"""
self.label.setText(str(element))
self.element = element
- try:
- element._draggable
- except AttributeError:
- pass
self.button_add_annotation.hide()
self.button_add_rectangle.hide()
@@ -1344,7 +1620,7 @@ def setElement(self, element: Artist):
else:
self.button_add_text.hide()
- if isinstance(element, mpl.legend.Legend):
+ if isinstance(element, Legend):
self.input_legend_properties.show()
self.input_legend_properties.setTarget(element)
else:
@@ -1353,12 +1629,13 @@ def setElement(self, element: Artist):
try:
self.input_font_properties.show()
elements = [element]
- elements += [element.target for element in main_figure(element).selection.targets if
- element.target != element]
+ elements += [
+ element.target
+ for element in main_figure(element).selection.targets
+ if element.target != element
+ ]
self.input_font_properties.setTarget(elements)
except AttributeError:
self.input_font_properties.hide()
self.targetChanged.emit(element)
-
-
diff --git a/pylustrator/components/qpos_and_size.py b/pylustrator/components/qpos_and_size.py
index 95c8943..8b90498 100644
--- a/pylustrator/components/qpos_and_size.py
+++ b/pylustrator/components/qpos_and_size.py
@@ -1,16 +1,20 @@
-from typing import Optional
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from typing import TYPE_CHECKING, Optional
+
+if TYPE_CHECKING:
+ from PyQt5 import QtWidgets
+else:
+ from qtpy import QtWidgets
import matplotlib as mpl
import matplotlib.transforms as transforms
from matplotlib.figure import Figure
from matplotlib.artist import Artist
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
- from matplotlib.axes._subplots import Axes
+except ImportError:
+ from matplotlib.axes._subplots import Axes # ty:ignore[unresolved-import]
from matplotlib.text import Text
-import matplotlib.transforms as transforms
from pylustrator.helper_functions import changeFigureSize, main_figure
from pylustrator.QLinkableWidgets import DimensionsWidget, ComboWidget
@@ -22,8 +26,8 @@ class QPosAndSize(QtWidgets.QWidget):
transform_index = 0
scale_type = 0
- def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
- """ a widget that holds all the properties to set and the tree view
+ def __init__(self, layout: QtWidgets.QLayout, signals):
+ """a widget that holds all the properties to set and the tree view
Args:
layout: the layout to which to add the widget
@@ -37,64 +41,72 @@ def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
self.signals = signals
layout.addWidget(self)
- self.layout = QtWidgets.QHBoxLayout(self)
- self.layout.setContentsMargins(10, 0, 10, 0)
+ self.layout_main = QtWidgets.QHBoxLayout(self)
+ self.layout_main.setContentsMargins(10, 0, 10, 0)
- self.input_position = DimensionsWidget(self.layout, "X:", "Y:", "cm")
- self.input_position.valueChangedX.connect(lambda x: self.changePos(x, None))
- self.input_position.valueChangedY.connect(lambda y: self.changePos(None, y))
+ self.input_position = DimensionsWidget(self.layout_main, "X:", "Y:", "cm")
+ self.input_position.valueChangedX.connect(lambda x: self.changePos(x, None)) # ty:ignore[invalid-argument-type]
+ self.input_position.valueChangedY.connect(lambda y: self.changePos(None, y)) # ty:ignore[invalid-argument-type]
- self.input_shape = DimensionsWidget(self.layout, "W:", "H:", "cm")
+ self.input_shape = DimensionsWidget(self.layout_main, "W:", "H:", "cm")
self.input_shape.valueChanged.connect(self.changeSize)
- self.input_transform = ComboWidget(self.layout, "", ["cm", "in", "px", "none"])
+ self.input_transform = ComboWidget(
+ self.layout_main, "", ["cm", "in", "px", "none"]
+ )
self.input_transform.editingFinished.connect(self.changeTransform)
- self.input_shape_transform = ComboWidget(self.layout, "", ["scale", "bottom right", "top left"])
+ self.input_shape_transform = ComboWidget(
+ self.layout_main, "", ["scale", "bottom right", "top left"]
+ )
self.input_shape_transform.editingFinished.connect(self.changeTransform2)
- self.layout.addStretch()
+ self.layout_main.addStretch()
def setFigure(self, figure):
self.fig = figure
def select_element(self, element):
- """ select an element """
+ """select an element"""
if element is None:
self.setElement(self.fig)
else:
self.setElement(element)
def selection_moved(self):
- self.setElement(self.element)
+ self.setElement(self.element) # ty:ignore[invalid-argument-type]
def changeTransform(self):
- """ change the transform and the units of the position and size widgets """
+ """change the transform and the units of the position and size widgets"""
name = self.input_transform.text()
- self.transform_index = ["cm", "in", "px", "none"].index(name)#transform_index
+ self.transform_index = ["cm", "in", "px", "none"].index(name) # transform_index
if name == "none":
name = ""
self.input_shape.setUnit(name)
self.input_position.setUnit(name)
- self.setElement(self.element)
+ self.setElement(self.element) # ty:ignore[invalid-argument-type]
- def changeTransform2(self):#, state: int, name: str):
- """ when the dimension change type is changed from 'scale' to 'bottom right' or 'bottom left' """
+ def changeTransform2(self): # , state: int, name: str):
+ """when the dimension change type is changed from 'scale' to 'bottom right' or 'bottom left'"""
name = self.input_shape_transform.text()
self.scale_type = ["scale", "bottom right", "top left"].index(name)
- #self.scale_type = state
+ # self.scale_type = state
def changePos(self, value_x: float, value_y: float):
- """ change the position of an axes """
+ """change the position of an axes"""
elements = [self.element]
- elements += [element.target for element in main_figure(self.element).selection.targets]
+ elements += [
+ element.target for element in main_figure(self.element).selection.targets
+ ]
old_positions = []
new_positions = []
for element in elements:
- old_pos = element.get_position()
+ old_pos = element.get_position() # ty:ignore[possibly-missing-attribute]
try:
- old_positions.append([old_pos.x0, old_pos.y0, old_pos.width, old_pos.height])
+ old_positions.append(
+ [old_pos.x0, old_pos.y0, old_pos.width, old_pos.height]
+ )
except AttributeError:
old_positions.append(old_pos)
if getattr(old_pos, "width", None) is not None:
@@ -117,9 +129,13 @@ def redo():
elif isinstance(element, Axes):
fig.change_tracker.addNewAxesChange(element)
elif len(pos) == 4:
- fig.change_tracker.addChange(element, ".set_position([%f, %f, %f, %f])" % tuple(pos))
+ fig.change_tracker.addChange(
+ element, ".set_position([%f, %f, %f, %f])" % tuple(pos)
+ )
else:
- fig.change_tracker.addChange(element, ".set_position([%f, %f])" % tuple(pos))
+ fig.change_tracker.addChange(
+ element, ".set_position([%f, %f])" % tuple(pos)
+ )
def undo():
for element, pos in zip(elements, old_positions):
@@ -129,9 +145,13 @@ def undo():
elif isinstance(element, Axes):
fig.change_tracker.addNewAxesChange(element)
elif len(pos) == 4:
- fig.change_tracker.addChange(element, ".set_position([%f, %f, %f, %f])" % tuple(pos))
+ fig.change_tracker.addChange(
+ element, ".set_position([%f, %f, %f, %f])" % tuple(pos)
+ )
else:
- fig.change_tracker.addChange(element, ".set_position([%f, %f])" % tuple(pos))
+ fig.change_tracker.addChange(
+ element, ".set_position([%f, %f])" % tuple(pos)
+ )
redo()
self.fig.change_tracker.addEdit([undo, redo, "Change position"])
@@ -140,27 +160,43 @@ def undo():
self.fig.canvas.draw()
def changeSize(self, value: list):
- """ change the size of an axes or figure """
+ """change the size of an axes or figure"""
if isinstance(self.element, Figure):
-
if self.scale_type == 0:
self.fig.set_size_inches(value)
- self.fig.change_tracker.addChange(self.element, ".set_size_inches(%f/2.54, %f/2.54, forward=True)" % (
- value[0] * 2.54, value[1] * 2.54))
+ self.fig.change_tracker.addChange(
+ self.element,
+ ".set_size_inches(%f/2.54, %f/2.54, forward=True)"
+ % (value[0] * 2.54, value[1] * 2.54),
+ )
else:
if self.scale_type == 1:
changeFigureSize(value[0], value[1], fig=self.fig)
elif self.scale_type == 2:
- changeFigureSize(value[0], value[1], cut_from_top=True, cut_from_left=True, fig=self.fig)
- self.fig.change_tracker.addChange(self.element, ".set_size_inches(%f/2.54, %f/2.54, forward=True)" % (
- value[0] * 2.54, value[1] * 2.54))
+ changeFigureSize(
+ value[0],
+ value[1],
+ cut_from_top=True,
+ cut_from_left=True,
+ fig=self.fig,
+ )
+ self.fig.change_tracker.addChange(
+ self.element,
+ ".set_size_inches(%f/2.54, %f/2.54, forward=True)"
+ % (value[0] * 2.54, value[1] * 2.54),
+ )
for axes in self.fig.axes:
pos = axes.get_position()
- self.fig.change_tracker.addChange(axes, ".set_position([%f, %f, %f, %f])" % (
- pos.x0, pos.y0, pos.width, pos.height))
+ self.fig.change_tracker.addChange(
+ axes,
+ ".set_position([%f, %f, %f, %f])"
+ % (pos.x0, pos.y0, pos.width, pos.height),
+ )
for text in self.fig.texts:
pos = text.get_position()
- self.fig.change_tracker.addChange(text, ".set_position([%f, %f])" % (pos[0], pos[1]))
+ self.fig.change_tracker.addChange(
+ text, ".set_position([%f, %f])" % (pos[0], pos[1])
+ )
self.fig.selection.update_selection_rectangles()
self.fig.canvas.draw()
@@ -170,13 +206,16 @@ def changeSize(self, value: list):
self.signals.figure_size_changed.emit()
else:
elements = [self.element]
- elements += [element.target for element in self.element.figure.selection.targets if
- element.target != self.element and isinstance(element.target, Axes)]
+ elements += [
+ element.target
+ for element in self.element.figure.selection.targets # ty:ignore[possibly-missing-attribute]
+ if element.target != self.element and isinstance(element.target, Axes)
+ ]
old_positions = []
new_positions = []
for element in elements:
- pos = element.get_position()
+ pos = element.get_position() # ty:ignore[possibly-missing-attribute]
old_positions.append(pos)
pos = [pos.x0, pos.y0, pos.width, pos.height]
pos[2] = value[0]
@@ -191,7 +230,9 @@ def redo():
if isinstance(element, Axes):
fig.change_tracker.addNewAxesChange(element)
else:
- fig.change_tracker.addChange(element, ".set_position([%f, %f, %f, %f])" % tuple(pos))
+ fig.change_tracker.addChange(
+ element, ".set_position([%f, %f, %f, %f])" % tuple(pos)
+ )
def undo():
for element, pos in zip(elements, new_positions):
@@ -199,32 +240,42 @@ def undo():
if isinstance(element, Axes):
fig.change_tracker.addNewAxesChange(element)
else:
- fig.change_tracker.addChange(element, ".set_position([%f, %f, %f, %f])" % tuple(pos))
+ fig.change_tracker.addChange(
+ element, ".set_position([%f, %f, %f, %f])" % tuple(pos)
+ )
redo()
self.fig.change_tracker.addEdit([undo, redo, "Change size"])
self.fig.signals.figure_selection_property_changed.emit()
self.fig.canvas.draw()
-
def getTransform(self, element: Artist) -> Optional[mpl.transforms.Transform]:
- """ get the transform of an Artist """
+ """get the transform of an Artist"""
if isinstance(element, Figure):
if self.transform_index == 0:
return transforms.Affine2D().scale(2.54, 2.54)
return None
if isinstance(element, Axes):
if self.transform_index == 0:
- return transforms.Affine2D().scale(2.54,
- 2.54) + element.figure.dpi_scale_trans.inverted() + element.figure.transFigure
+ return (
+ transforms.Affine2D().scale(2.54, 2.54)
+ + element.figure.dpi_scale_trans.inverted()
+ + element.figure.transFigure
+ )
if self.transform_index == 1:
- return element.figure.dpi_scale_trans.inverted() + element.figure.transFigure
+ return (
+ element.figure.dpi_scale_trans.inverted()
+ + element.figure.transFigure
+ )
if self.transform_index == 2:
return element.figure.transFigure
return None
if self.transform_index == 0:
- return transforms.Affine2D().scale(2.54,
- 2.54) + element.figure.dpi_scale_trans.inverted() + element.get_transform()
+ return (
+ transforms.Affine2D().scale(2.54, 2.54)
+ + element.figure.dpi_scale_trans.inverted()
+ + element.get_transform()
+ )
if self.transform_index == 1:
return element.figure.dpi_scale_trans.inverted() + element.get_transform()
if self.transform_index == 2:
@@ -232,8 +283,8 @@ def getTransform(self, element: Artist) -> Optional[mpl.transforms.Transform]:
return None
def setElement(self, element: Artist):
- """ set the target Artist of this widget """
- #self.label.setText(str(element))
+ """set the target Artist of this widget"""
+ # self.label.setText(str(element))
self.element = element
self.input_shape_transform.setDisabled(True)
@@ -257,13 +308,13 @@ def setElement(self, element: Artist):
self.input_shape.setDisabled(True)
try:
- pos = element.get_position()
+ pos = element.get_position() # ty:ignore[unresolved-attribute]
self.input_position.setTransform(self.getTransform(element))
try:
self.input_position.setValue(pos)
- except Exception as err:
+ except (ValueError, TypeError):
self.input_position.setValue((pos.x0, pos.y0))
self.input_transform.setEnabled(True)
self.input_position.setEnabled(True)
- except:
- self.input_position.setDisabled(True)
\ No newline at end of file
+ except (AttributeError, RuntimeError):
+ self.input_position.setDisabled(True)
diff --git a/pylustrator/components/tree_view.py b/pylustrator/components/tree_view.py
index 454e876..cbd59ad 100644
--- a/pylustrator/components/tree_view.py
+++ b/pylustrator/components/tree_view.py
@@ -1,41 +1,60 @@
-from typing import Optional
+from typing import TYPE_CHECKING, Optional
import qtawesome as qta
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-import matplotlib as mpl
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
+else:
+ from qtpy import QtCore, QtGui, QtWidgets
+
from matplotlib.artist import Artist
+from matplotlib.spines import Spine
+from matplotlib.axis import XAxis, YAxis
+from matplotlib.text import Text
class myTreeWidgetItem(QtGui.QStandardItem):
- def __init__(self, parent: QtWidgets.QWidget = None):
- """ a tree view item to display the contents of the figure """
- QtGui.QStandardItem.__init__(self, parent)
+ entry: Artist | None = None
+ expanded: bool = False
+
+ def __init__(self, text: str):
+ """a tree view item to display the contents of the figure"""
+ QtGui.QStandardItem.__init__(self, text)
- def __lt__(self, otherItem: QtGui.QStandardItem):
- """ how to sort the items """
- if self.sort is None:
- return 0
- return self.sort < otherItem.sort
+ def parent(self) -> Optional["myTreeWidgetItem"]:
+ parent = super().parent()
+ if parent is None:
+ return parent
+ if isinstance(parent, myTreeWidgetItem):
+ return parent
+ raise TypeError("myTreeWidgetItem has invalid parent")
class MyTreeView(QtWidgets.QTreeView):
- #item_selected = lambda x, y: 0
- item_clicked = lambda x, y: 0
- item_activated = lambda x, y: 0
- item_hoverEnter = lambda x, y: 0
- item_hoverLeave = lambda x, y: 0
+ # item_selected = lambda x, y: 0
+ def item_clicked(x, y):
+ return 0
+
+ def item_activated(x, y):
+ return 0
+
+ def item_hoverEnter(x, y):
+ return 0
+
+ def item_hoverLeave(x, y):
+ return 0
last_selection = None
last_hover = None
+ model: QtGui.QStandardItemModel
def item_selected(self, x):
if not self.fig.no_figure_dragger_selection_update:
if getattr(self.fig, "figure_dragger", None) is not None:
self.fig.figure_dragger.select_element(x)
- def __init__(self, signals: "Signals", layout: QtWidgets.QLayout):
- """ A tree view to display the contents of a figure
+ def __init__(self, signals, layout: QtWidgets.QLayout):
+ """A tree view to display the contents of a figure
Args:
parent: the parent widget
@@ -43,11 +62,13 @@ def __init__(self, signals: "Signals", layout: QtWidgets.QLayout):
fig: the target figure
"""
super().__init__()
- #self.setMaximumWidth(300)
+ # self.setMaximumWidth(300)
signals.figure_changed.connect(self.setFigure)
signals.figure_element_selected.connect(self.select_element)
- signals.figure_element_child_created.connect(lambda x: self.updateEntry(x, update_children=True))
+ signals.figure_element_child_created.connect(
+ lambda x: self.updateEntry(x, update_children=True)
+ )
layout.addWidget(self)
@@ -66,19 +87,26 @@ def __init__(self, signals: "Signals", layout: QtWidgets.QLayout):
self.expanded.connect(self.TreeExpand)
self.clicked.connect(self.treeClicked)
self.activated.connect(self.treeActivated)
- self.selectionModel().selectionChanged.connect(self.selectionChanged)
+
+ selectionModel = self.selectionModel()
+ if selectionModel is None:
+ raise ValueError("")
+ selectionModel.selectionChanged.connect(self.selectionChanged)
# add context menu
- self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
# add hover highlight
- self.viewport().setMouseTracking(True)
- self.viewport().installEventFilter(self)
+ viewport = self.viewport()
+ if viewport is None:
+ raise ValueError("")
+ viewport.setMouseTracking(True)
+ viewport.installEventFilter(self)
self.item_lookup = {}
def select_element(self, element: Artist):
- """ select an element """
+ """select an element"""
if element is None:
self.setCurrentIndex(self.fig)
else:
@@ -94,18 +122,24 @@ def setFigure(self, fig):
self.expand(self.fig)
self.setCurrentIndex(self.fig)
- def selectionChanged(self, selection: QtCore.QItemSelection, y: QtCore.QItemSelection):
- """ when the selection in the tree view changes """
- try:
- entry = selection.indexes()[0].model().itemFromIndex(selection.indexes()[0]).entry
- except IndexError:
- entry = None
+ def selectionChanged(
+ self, selection: QtCore.QItemSelection, y: QtCore.QItemSelection
+ ):
+ """when the selection in the tree view changes"""
+ model = None
+ if len(selection.indexes()):
+ model = selection.indexes()[0].model()
+ entry = None
+ if model is not None and isinstance(model, QtGui.QStandardItemModel):
+ selected_entry = model.itemFromIndex(selection.indexes()[0])
+ if isinstance(selected_entry, myTreeWidgetItem):
+ entry = selected_entry.entry
if self.last_selection != entry:
self.last_selection = entry
self.item_selected(entry)
def setCurrentIndex(self, entry: Artist):
- """ set the currently selected entry """
+ """set the currently selected entry"""
while entry:
item = self.getItemFromEntry(entry)
if item is not None:
@@ -114,35 +148,60 @@ def setCurrentIndex(self, entry: Artist):
except RuntimeError: # maybe find out why we run into this error when the figure is changed
pass
return
- try:
- entry = entry.tree_parent
- except AttributeError:
+
+ tree_parent = getattr(entry, "tree_parent", None)
+ if isinstance(tree_parent, Artist):
+ entry = tree_parent
+ else:
return
def treeClicked(self, index: QtCore.QModelIndex):
- """ upon selecting one of the tree elements """
- data = index.model().itemFromIndex(index).entry
- return self.item_clicked(data)
+ """upon selecting one of the tree elements"""
+ model = index.model()
+ if model is not None and isinstance(model, QtGui.QStandardItemModel):
+ selected_entry = model.itemFromIndex(index)
+ if isinstance(selected_entry, myTreeWidgetItem):
+ data = selected_entry.entry
+ return self.item_clicked(data)
+ return self.item_clicked(None)
def treeActivated(self, index: QtCore.QModelIndex):
- """ upon selecting one of the tree elements """
- data = index.model().itemFromIndex(index).entry
- return self.item_activated(data)
-
- def eventFilter(self, object: QtWidgets.QWidget, event: QtCore.QEvent):
- """ event filter for tree view port to handle mouse over events and marker highlighting"""
- if event.type() == QtCore.QEvent.HoverMove:
- index = self.indexAt(event.pos())
+ """upon selecting one of the tree elements"""
+ model = index.model()
+ if model is not None and isinstance(model, QtGui.QStandardItemModel):
+ selected_entry = model.itemFromIndex(index)
+ if isinstance(selected_entry, myTreeWidgetItem):
+ data = selected_entry.entry
+ return self.item_activated(data)
+ return self.item_activated(None)
+
+ def eventFilter(
+ self, a0: Optional[QtCore.QObject], a1: Optional[QtCore.QEvent]
+ ) -> bool:
+ """event filter for tree view port to handle mouse over events and marker highlighting"""
+ if a1 is None:
+ return False
+ if a1.type() == QtCore.QEvent.Type.HoverMove and isinstance(
+ a1, QtGui.QHoverEvent
+ ):
+ # HoverMove events have pos() method
+ pos = a1.pos()
+ index = self.indexAt(pos)
try:
- item = index.model().itemFromIndex(index)
- entry = item.entry
- except:
- item = None
+ model = index.model()
+ if isinstance(model, QtGui.QStandardItemModel):
+ item = model.itemFromIndex(index)
+ if isinstance(item, myTreeWidgetItem):
+ entry = item.entry
+ else:
+ entry = None
+ else:
+ entry = None
+ except AttributeError:
entry = None
# check for new item
if entry != self.last_hover:
-
# deactivate last hover item
if self.last_hover is not None:
self.item_hoverLeave(self.last_hover)
@@ -157,38 +216,36 @@ def eventFilter(self, object: QtWidgets.QWidget, event: QtCore.QEvent):
return False
def queryToExpandEntry(self, entry: Artist) -> list:
- """ when expanding a tree item """
+ """when expanding a tree item"""
if entry is None:
return [self.fig]
return entry.get_children()
- def getParentEntry(self, entry: Artist) -> Artist:
- """ get the parent of an item """
+ def getParentEntry(self, entry: Artist) -> Artist | None:
+ """get the parent of an item"""
return getattr(entry, "tree_parent", None)
- def getNameOfEntry(self, entry: Artist) -> str:
- """ convert an entry to a string """
+ def getNameOfEntry(self, entry: Artist | None) -> str:
+ """convert an entry to a string"""
try:
return str(entry)
except AttributeError:
return "unknown"
def getIconOfEntry(self, entry: Artist) -> QtGui.QIcon:
- """ get the icon of an entry """
- if getattr(entry, "_draggable", None):
- if entry._draggable.connected:
+ """get the icon of an entry"""
+ draggable = getattr(entry, "_draggable", None)
+ if draggable:
+ if draggable.connected:
return qta.icon("fa5.hand-paper-o")
return QtGui.QIcon()
- def getEntrySortRole(self, entry: Artist):
- return None
-
def getKey(self, entry: Artist) -> Artist:
- """ get the key of an entry, which is the entry itself """
+ """get the key of an entry, which is the entry itself"""
return entry
- def getItemFromEntry(self, entry: Artist) -> Optional[QtWidgets.QTreeWidgetItem]:
- """ get the tree view item for the given artist """
+ def getItemFromEntry(self, entry: Artist | None) -> Optional[myTreeWidgetItem]:
+ """get the tree view item for the given artist"""
if entry is None:
return None
key = self.getKey(entry)
@@ -197,13 +254,15 @@ def getItemFromEntry(self, entry: Artist) -> Optional[QtWidgets.QTreeWidgetItem]
except KeyError:
return None
- def setItemForEntry(self, entry: Artist, item: QtWidgets.QTreeWidgetItem):
- """ store a new artist and tree view widget pair """
+ def setItemForEntry(self, entry: Artist, item: myTreeWidgetItem):
+ """store a new artist and tree view widget pair"""
key = self.getKey(entry)
self.item_lookup[key] = item
- def expand(self, entry: Artist, force_reload: bool = True):
- """ expand the children of a tree view item """
+ def expand(self, entry: Artist | None, force_reload: bool = True):
+ """expand the children of a tree view item"""
+ if entry is None:
+ return
query = self.queryToExpandEntry(entry)
parent_item = self.getItemFromEntry(entry)
parent_entry = entry
@@ -225,17 +284,19 @@ def expand(self, entry: Artist, force_reload: bool = True):
for row, entry in enumerate(query):
entry.tree_parent = parent_entry
if 1:
- if (isinstance(entry, mpl.spines.Spine) or
- isinstance(entry, mpl.axis.XAxis) or
- isinstance(entry, mpl.axis.YAxis)):
+ if (
+ isinstance(entry, Spine)
+ or isinstance(entry, XAxis)
+ or isinstance(entry, YAxis)
+ ):
continue
- if isinstance(entry, mpl.text.Text) and entry.get_text() == "":
+ if isinstance(entry, Text) and entry.get_text() == "":
continue
- try:
- if entry == parent_entry.patch:
- continue
- except AttributeError:
- pass
+
+ patch = getattr(parent_entry, "patch", None)
+ if patch and entry == patch:
+ continue
+
try:
label = entry.get_label()
if label == "_tmp_snap" or label == "grabber":
@@ -244,11 +305,8 @@ def expand(self, entry: Artist, force_reload: bool = True):
pass
self.addChild(parent_item, entry)
- def addChild(self, parent_item: QtWidgets.QWidget, entry: Artist, row=None):
- """ add a child to a tree view node """
- if parent_item is None:
- parent_item = self.model
-
+ def addChild(self, parent_item: myTreeWidgetItem | None, entry: Artist, row=None):
+ """add a child to a tree view node"""
# add item
item = myTreeWidgetItem(self.getNameOfEntry(entry))
item.expanded = False
@@ -256,7 +314,6 @@ def addChild(self, parent_item: QtWidgets.QWidget, entry: Artist, row=None):
item.setIcon(self.getIconOfEntry(entry))
item.setEditable(False)
- item.sort = self.getEntrySortRole(entry)
if parent_item is None:
if row is None:
@@ -271,8 +328,10 @@ def addChild(self, parent_item: QtWidgets.QWidget, entry: Artist, row=None):
self.setItemForEntry(entry, item)
# add dummy child
- if self.queryToExpandEntry(entry) is not None and len(self.queryToExpandEntry(entry)):
- child = QtGui.QStandardItem("loading")
+ if self.queryToExpandEntry(entry) is not None and len(
+ self.queryToExpandEntry(entry)
+ ):
+ child = myTreeWidgetItem("loading")
child.entry = None
child.setEditable(False)
child.setIcon(qta.icon("fa5s.hourglass-half"))
@@ -281,7 +340,7 @@ def addChild(self, parent_item: QtWidgets.QWidget, entry: Artist, row=None):
return item
def TreeExpand(self, index):
- """ expand a tree view node """
+ """expand a tree view node"""
# Get item and entry
item = index.model().itemFromIndex(index)
entry = item.entry
@@ -297,8 +356,14 @@ def TreeExpand(self, index):
thread.setDaemon(True)
thread.start()
- def updateEntry(self, entry: Artist, update_children: bool = False, insert_before: Artist = None, insert_after: Artist = None):
- """ update a tree view node """
+ def updateEntry(
+ self,
+ entry: Artist,
+ update_children: bool = False,
+ insert_before: Artist | None = None,
+ insert_after: Artist | None = None,
+ ):
+ """update a tree view node"""
# get the tree view item for the database entry
item = self.getItemFromEntry(entry)
# if we haven't one yet, we have to create it
@@ -319,9 +384,13 @@ def updateEntry(self, entry: Artist, update_children: bool = False, insert_befor
# define the row where the new item should be
row = None
if insert_before:
- row = self.getItemFromEntry(insert_before).row()
+ before_entry = self.getItemFromEntry(insert_before)
+ if before_entry:
+ row = before_entry.row()
if insert_after:
- row = self.getItemFromEntry(insert_after).row() + 1
+ after_entry = self.getItemFromEntry(insert_after)
+ if after_entry:
+ row = after_entry.row() + 1
# add the item as a child of its parent
self.addChild(parent_item, entry, row)
@@ -336,17 +405,22 @@ def updateEntry(self, entry: Artist, update_children: bool = False, insert_befor
parent_item = self.getItemFromEntry(parent_entry)
if parent_item != item.parent():
# remove the item from the old position
- if item.parent() is None:
+ item_parent = item.parent()
+ if item_parent is None:
self.model.takeRow(item.row())
else:
- item.parent().takeRow(item.row())
+ item_parent.takeRow(item.row())
# determine a potential new position
row = None
if insert_before:
- row = self.getItemFromEntry(insert_before).row()
+ before_entry = self.getItemFromEntry(insert_before)
+ if before_entry:
+ row = before_entry.row()
if insert_after:
- row = self.getItemFromEntry(insert_after).row() + 1
+ after_entry = self.getItemFromEntry(insert_after)
+ if after_entry:
+ row = after_entry.row() + 1
# move the item to the new position
if parent_item is None:
@@ -367,15 +441,15 @@ def updateEntry(self, entry: Artist, update_children: bool = False, insert_befor
self.expand(entry, force_reload=True)
def deleteEntry(self, entry: Artist):
- """ delete an entry from the tree """
+ """delete an entry from the tree"""
# get the tree view item for the database entry
item = self.getItemFromEntry(entry)
- if item is None:
+ if item is None or not isinstance(item, myTreeWidgetItem):
return
parent_item = item.parent()
- if parent_item:
- parent_entry = parent_item.entry
+ # if parent_item:
+ # parent_entry = parent_item.entry
key = self.getKey(entry)
del self.item_lookup[key]
@@ -384,10 +458,11 @@ def deleteEntry(self, entry: Artist):
if parent_item is None:
self.model.removeRow(item.row())
else:
- item.parent().removeRow(item.row(), item.parent())
-
- # update the label of parent item
- if parent_item:
- name = self.getNameOfEntry(parent_entry)
- if name is not None:
- parent_item.setLabel(name)
\ No newline at end of file
+ parent_item.removeRow(item.row())
+ # parent_item.removeRow(item.row(), parent_entry) # this is not working
+
+ # update the label of parent item ## parent item does not have an attribute setLabel
+ # if parent_item:
+ # name = self.getNameOfEntry(parent_entry)
+ # if name is not None:
+ # parent_item.setLabel(name)
diff --git a/pylustrator/drag_helper.py b/pylustrator/drag_helper.py
index eb9739a..05e7ab9 100644
--- a/pylustrator/drag_helper.py
+++ b/pylustrator/drag_helper.py
@@ -20,15 +20,19 @@
# along with Pylustrator. If not, see
import numpy as np
-import matplotlib.pyplot as plt
from matplotlib.artist import Artist
from matplotlib.figure import Figure, SubFigure
from matplotlib.axes import Axes
from matplotlib.text import Text
-from matplotlib.patches import Rectangle, Ellipse
-from matplotlib.backend_bases import MouseEvent, KeyEvent
-from typing import Sequence
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from matplotlib.patches import Rectangle
+from matplotlib.backend_bases import MouseEvent, KeyEvent, Event
+from typing import TYPE_CHECKING, Sequence, Callable, Tuple, List, Any, cast, Type
+
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
+ from .components.plot_layout import GraphicsRectItemWithView
+else:
+ from qtpy import QtCore, QtGui, QtWidgets
from .snap import TargetWrapper, getSnaps, checkSnaps, checkSnapsActive, SnapBase
from .change_tracker import ChangeTracker
@@ -44,44 +48,51 @@
class GrabFunctions(object):
- """ basic functionality used by all grabbers """
- figure = None
+ """basic functionality used by all grabbers"""
+
+ figure: Figure
target = None
- dir = None
- snaps = None
+ dir: int
+ snaps: list[SnapBase]
+ targets: list[TargetWrapper]
got_artist = False
def __init__(self, parent, dir: int, no_height=False):
- self.figure = parent.figure
+ figure: Figure = parent.figure
+ if not isinstance(figure, Figure):
+ raise TypeError()
+ self.figure = figure
self.parent = parent
self.dir = dir
self.snaps = []
self.no_height = no_height
- def on_motion(self, evt: MouseEvent):
- """ callback when the object is moved """
+ def on_motion(self, evt: Event):
+ """callback when the object is moved"""
+ if not isinstance(evt, MouseEvent):
+ raise TypeError()
if self.got_artist:
self.movedEvent(evt)
self.moved = True
def button_press_event(self, evt: MouseEvent):
- """ when the mouse is pressed """
+ """when the mouse is pressed"""
self.got_artist = True
self.moved = False
- self._c1 = self.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
+ self._c1 = self.figure.canvas.mpl_connect("motion_notify_event", self.on_motion)
self.clickedEvent(evt)
def button_release_event(self, event: MouseEvent):
- """ when the mouse is released """
+ """when the mouse is released"""
if self.got_artist:
self.got_artist = False
self.figure.canvas.mpl_disconnect(self._c1)
self.releasedEvent(event)
def clickedEvent(self, event: MouseEvent):
- """ when the mouse is clicked """
+ """when the mouse is clicked"""
self.parent.start_move()
self.mouse_xy = (event.x, event.y)
@@ -102,7 +113,7 @@ def clickedEvent(self, event: MouseEvent):
self.time = time.time()
def releasedEvent(self, event: MouseEvent):
- """ when the mouse is released """
+ """when the mouse is released"""
for snap in self.snaps:
snap.remove()
self.snaps = []
@@ -116,17 +127,27 @@ def releasedEvent(self, event: MouseEvent):
pass
def movedEvent(self, event: MouseEvent):
- """ when the mouse is moved """
+ """when the mouse is moved"""
if len(self.targets) == 0:
return
dx = event.x - self.mouse_xy[0]
dy = event.y - self.mouse_xy[1]
- keep_aspect = ("control" in event.key.split("+") if event.key is not None else False)
- ignore_snaps = ("shift" in event.key.split("+") if event.key is not None else False)
-
- self.parent.move([dx, dy], self.dir, self.snaps, keep_aspect_ratio=keep_aspect, ignore_snaps=ignore_snaps)
+ keep_aspect = (
+ "control" in event.key.split("+") if event.key is not None else False
+ )
+ ignore_snaps = (
+ "shift" in event.key.split("+") if event.key is not None else False
+ )
+
+ self.parent.move(
+ [dx, dy],
+ self.dir,
+ self.snaps,
+ keep_aspect_ratio=keep_aspect,
+ ignore_snaps=ignore_snaps,
+ )
if blit is True:
fig = self.figure
@@ -140,14 +161,17 @@ def movedEvent(self, event: MouseEvent):
else:
self.figure.canvas.schedule_draw()
+
class GrabbableRectangleSelection(GrabFunctions):
- grabbers = None
+ grabbers: list["GrabberGeneric"]
- def addGrabber(self, x: float, y: float, dir: int, GrabberClass: object):
+ def addGrabber(
+ self, x: float, y: float, dir: int, GrabberClass: Type["GrabberGeneric"]
+ ):
# add a grabber object at the given coordinates
self.grabbers.append(GrabberClass(self, x, y, dir, self.graphics_scene))
- def __init__(self, figure: Figure, graphics_scene=None):
+ def __init__(self, figure: Figure, graphics_scene: "GraphicsRectItemWithView"):
self.grabbers = []
pos = [0, 0, 0, 0]
self.positions = np.array(pos, dtype=float)
@@ -155,52 +179,83 @@ def __init__(self, figure: Figure, graphics_scene=None):
self.p2 = self.positions[2:]
self.figure = figure
self.graphics_scene = graphics_scene
- self.graphics_scene_myparent = QtWidgets.QGraphicsRectItem(0, 0, 0, 0, self.graphics_scene)
- self.graphics_scene_snapparent = QtWidgets.QGraphicsRectItem(0, 0, 0, 0, self.graphics_scene)
+ self.graphics_scene_myparent = QtWidgets.QGraphicsRectItem(
+ 0, 0, 0, 0, self.graphics_scene
+ )
+ self.graphics_scene_snapparent = QtWidgets.QGraphicsRectItem(
+ 0, 0, 0, 0, self.graphics_scene
+ )
figure._pyl_graphics_scene_snapparent = self.graphics_scene_snapparent
+ GrabFunctions.__init__(
+ self, self, DIR_X0 | DIR_X1 | DIR_Y0 | DIR_Y1, no_height=True
+ )
- GrabFunctions.__init__(self, self, DIR_X0 | DIR_X1 | DIR_Y0 | DIR_Y1, no_height=True)
-
- self.addGrabber(0, 0, DIR_X0 | DIR_Y0, GrabberGenericRound)
+ self.addGrabber(0, 0, DIR_X0 | DIR_Y0, GrabberGenericRound)
self.addGrabber(0.5, 0, DIR_Y0, GrabberGenericRectangle)
- self.addGrabber(1, 1, DIR_X1 | DIR_Y1, GrabberGenericRound)
+ self.addGrabber(1, 1, DIR_X1 | DIR_Y1, GrabberGenericRound)
self.addGrabber(1, 0.5, DIR_X1, GrabberGenericRectangle)
- self.addGrabber(0, 1, DIR_X0 | DIR_Y1, GrabberGenericRound)
+ self.addGrabber(0, 1, DIR_X0 | DIR_Y1, GrabberGenericRound)
self.addGrabber(0.5, 1, DIR_Y1, GrabberGenericRectangle)
- self.addGrabber(1, 0, DIR_X1 | DIR_Y0, GrabberGenericRound)
+ self.addGrabber(1, 0, DIR_X1 | DIR_Y0, GrabberGenericRound)
self.addGrabber(0, 0.5, DIR_X0, GrabberGenericRectangle)
- self.c4 = self.figure.canvas.mpl_connect('key_press_event', self.keyPressEvent)
+ self.c4 = self.figure.canvas.mpl_connect("key_press_event", self.keyPressEvent)
- self.targets = []
- self.targets_rects = []
+ self.targets: list[TargetWrapper] = []
+ self.targets_rects: list[QtWidgets.QGraphicsRectItem] = []
self.hide_grabber()
def add_target(self, target: Artist):
- """ add an artist to the selection """
- target = TargetWrapper(target)
+ """add an artist to the selection"""
+ target_wrapped = TargetWrapper(target)
- new_points = np.array(target.get_positions())
+ new_points = np.array(target_wrapped.get_positions())
if len(new_points) == 0:
return
- self.targets.append(target)
+ self.targets.append(target_wrapped)
if new_points.shape[0] == 3:
- x0, y0, x1, y1 = np.min(new_points[1:, 0]), np.min(new_points[1:, 1]), np.max(
- new_points[1:, 0]), np.max(
- new_points[1:, 1])
+ x0, y0, x1, y1 = (
+ np.min(new_points[1:, 0]),
+ np.min(new_points[1:, 1]),
+ np.max(new_points[1:, 0]),
+ np.max(new_points[1:, 1]),
+ )
else:
- x0, y0, x1, y1 = np.min(new_points[:, 0]), np.min(new_points[:, 1]), np.max(new_points[:, 0]), np.max(
- new_points[:, 1])
+ x0, y0, x1, y1 = (
+ np.min(new_points[:, 0]),
+ np.min(new_points[:, 1]),
+ np.max(new_points[:, 0]),
+ np.max(new_points[:, 1]),
+ )
if 0:
-
- rect1 = Rectangle((x0, y0), x1 - x0, y1 - y0, picker=False, figure=self.figure, linestyle="-", edgecolor="w",
- facecolor="#FFFFFF00", zorder=900, label="_rect for %s" % str(target))
- rect2 = Rectangle((x0, y0), x1 - x0, y1 - y0, picker=False, figure=self.figure, linestyle="--", edgecolor="k",
- facecolor="#FFFFFF00", zorder=900, label="_rect2 for %s" % str(target))
+ rect1 = Rectangle(
+ (x0, y0),
+ x1 - x0,
+ y1 - y0,
+ picker=False,
+ figure=self.figure,
+ linestyle="-",
+ edgecolor="w",
+ facecolor="#FFFFFF00",
+ zorder=900,
+ label="_rect for %s" % str(target),
+ )
+ rect2 = Rectangle(
+ (x0, y0),
+ x1 - x0,
+ y1 - y0,
+ picker=False,
+ figure=self.figure,
+ linestyle="--",
+ edgecolor="k",
+ facecolor="#FFFFFF00",
+ zorder=900,
+ label="_rect2 for %s" % str(target),
+ )
self.figure.patches.append(rect1)
self.figure.patches.append(rect2)
self.targets_rects.append(rect1)
@@ -208,14 +263,18 @@ def add_target(self, target: Artist):
else:
pen1 = QtGui.QPen(QtGui.QColor("white"), 2)
pen2 = QtGui.QPen(QtGui.QColor("black"), 2)
- pen2.setStyle(QtCore.Qt.DashLine)
- pen3 = QtGui.QPen(QtGui.QColor("black"), 2)
- brush1 = QtGui.QBrush(QtGui.QColor("red"))
+ pen2.setStyle(QtCore.Qt.PenStyle.DashLine)
+ # pen3 = QtGui.QPen(QtGui.QColor("black"), 2)
+ # brush1 = QtGui.QBrush(QtGui.QColor("red"))
w0, h0 = x1 - x0, y1 - y0
- rect1 = QtWidgets.QGraphicsRectItem(x0, y0, w0, h0, self.graphics_scene_myparent)
+ rect1 = QtWidgets.QGraphicsRectItem(
+ x0, y0, w0, h0, self.graphics_scene_myparent
+ )
rect1.setPen(pen1)
- rect2 = QtWidgets.QGraphicsRectItem(x0, y0, w0, h0, self.graphics_scene_myparent)
+ rect2 = QtWidgets.QGraphicsRectItem(
+ x0, y0, w0, h0, self.graphics_scene_myparent
+ )
rect2.setPen(pen2)
self.targets_rects.append(rect1)
@@ -224,7 +283,7 @@ def add_target(self, target: Artist):
self.update_extent()
def update_extent(self):
- """ updates the extend of the selection to all the selected elements """
+ """updates the extend of the selection to all the selected elements"""
points = None
for target in self.targets:
new_points = np.array(target.get_positions())
@@ -245,10 +304,16 @@ def update_extent(self):
self.positions[2] = np.max(points[:, 0])
self.positions[3] = np.max(points[:, 1])
- if self.positions[2]-self.positions[0] < 0.01:
- self.positions[0], self.positions[2] = self.positions[0] - 0.01, self.positions[0] + 0.01
- if self.positions[3]-self.positions[1] < 0.01:
- self.positions[1], self.positions[3] = self.positions[1] - 0.01, self.positions[1] + 0.01
+ if self.positions[2] - self.positions[0] < 0.01:
+ self.positions[0], self.positions[2] = (
+ self.positions[0] - 0.01,
+ self.positions[0] + 0.01,
+ )
+ if self.positions[3] - self.positions[1] < 0.01:
+ self.positions[1], self.positions[3] = (
+ self.positions[1] - 0.01,
+ self.positions[1] + 0.01,
+ )
if self.do_target_scale():
self.update_grabber()
@@ -256,17 +321,32 @@ def update_extent(self):
self.hide_grabber()
def align_points(self, mode: str):
- """ a function to apply the alignment options, e.g. align all selected elements at the top or with equal spacing. """
+ """a function to apply the alignment options, e.g. align all selected elements at the top or with equal spacing."""
if len(self.targets) == 0:
return
if mode == "group":
from pylustrator.helper_functions import axes_to_grid
- #return axes_to_grid([target.target for target in self.targets], track_changes=True)
- with UndoRedo([target.target for target in self.targets if isinstance(target.target, Axes)], "Grid Align"):
- axes_to_grid([target.target for target in self.targets if isinstance(target.target, Axes)], track_changes=False)
- def align(y: int, func: callable):
+ # return axes_to_grid([target.target for target in self.targets], track_changes=True)
+ with UndoRedo(
+ [
+ target.target
+ for target in self.targets
+ if isinstance(target.target, Axes)
+ ],
+ "Grid Align",
+ ):
+ axes_to_grid(
+ [
+ target.target
+ for target in self.targets
+ if isinstance(target.target, Axes)
+ ],
+ track_changes=False,
+ )
+
+ def align(y: int, func: Callable):
self.start_move()
centers = []
for target in self.targets:
@@ -294,7 +374,7 @@ def distribute(y: int):
positions.append(np.min(new_points[:, y]))
order = np.argsort(positions)
spaces = np.diff(self.positions[y::2])[0] - np.sum(sizes)
- spaces /= max([(len(self.targets)-1), 1])
+ spaces /= max([(len(self.targets) - 1), 1])
pos = np.min(self.positions[y::2])
for index in order:
target = self.targets[index]
@@ -335,53 +415,65 @@ def distribute(y: int):
self.figure.signals.figure_selection_moved.emit()
def update_selection_rectangles(self, use_previous_offset=False):
- """ update the selection visualisation """
+ """update the selection visualisation"""
if len(self.targets) == 0:
return
if 0:
for index, target in enumerate(self.targets):
new_points = np.array(target.get_positions())
for i in range(2):
- rect = self.targets_rects[index*2+i]
+ rect = self.targets_rects[index * 2 + i]
rect.set_xy(new_points[0])
rect.set_width(new_points[1][0] - new_points[0][0])
rect.set_height(new_points[1][1] - new_points[0][1])
else:
for index, target in enumerate(self.targets):
- new_points = np.array(target.get_positions(use_previous_offset, update_offset=True))
+ new_points = np.array(
+ target.get_positions(use_previous_offset, update_offset=True)
+ )
if new_points.shape[0] == 3:
- x0, y0, x1, y1 = np.min(new_points[1:, 0]), np.min(new_points[1:, 1]), np.max(
- new_points[1:, 0]), np.max(
- new_points[1:, 1])
+ x0, y0, x1, y1 = (
+ np.min(new_points[1:, 0]),
+ np.min(new_points[1:, 1]),
+ np.max(new_points[1:, 0]),
+ np.max(new_points[1:, 1]),
+ )
else:
- x0, y0, x1, y1 = np.min(new_points[:, 0]), np.min(new_points[:, 1]), np.max(
- new_points[:, 0]), np.max(
- new_points[:, 1])
+ x0, y0, x1, y1 = (
+ np.min(new_points[:, 0]),
+ np.min(new_points[:, 1]),
+ np.max(new_points[:, 0]),
+ np.max(new_points[:, 1]),
+ )
w0, h0 = x1 - x0, y1 - y0
for i in range(2):
rect = self.targets_rects[index * 2 + i]
rect.setRect(x0, y0, w0, h0)
def remove_target(self, target: Artist):
- """ remove an artist from the current selection """
+ """remove an artist from the current selection"""
targets_non_wrapped = [t.target for t in self.targets]
if target not in targets_non_wrapped:
return
index = targets_non_wrapped.index(target)
self.targets.pop(index)
- rect1 = self.targets_rects.pop(index*2)
- rect2 = self.targets_rects.pop(index*2)
- rect1.scene().removeItem(rect1)
- rect2.scene().removeItem(rect2)
- #self.figure.patches.remove(rect1)
- #self.figure.patches.remove(rect2)
+ rect1 = self.targets_rects.pop(index * 2)
+ rect2 = self.targets_rects.pop(index * 2)
+ rect1_scene = rect1.scene()
+ if rect1_scene is not None:
+ rect1_scene.removeItem(rect1)
+ rect2_scene = rect1.scene()
+ if rect2_scene is not None:
+ rect2_scene.removeItem(rect2)
+ # self.figure.patches.remove(rect1)
+ # self.figure.patches.remove(rect2)
if len(self.targets) == 0:
self.clear_targets()
else:
self.update_extent()
def update_grabber(self):
- """ update the position of the grabber elements """
+ """update the position of the grabber elements"""
if self.do_target_scale():
for grabber in self.grabbers:
grabber.updatePos()
@@ -389,66 +481,70 @@ def update_grabber(self):
self.hide_grabber()
def hide_grabber(self):
- """ hide the grabber elements """
+ """hide the grabber elements"""
for grabber in self.grabbers:
grabber.set_xy((-100, -100))
def clear_targets(self):
- """ remove all elements from the selection """
+ """remove all elements from the selection"""
for rect in self.targets_rects:
- self.graphics_scene.scene().removeItem(rect)
- #self.figure.patches.remove(rect)
+ scene = self.graphics_scene.scene()
+ if scene is not None:
+ scene.removeItem(rect)
+ # self.figure.patches.remove(rect)
self.targets_rects = []
self.targets = []
self.hide_grabber()
def do_target_scale(self) -> bool:
- """ if any of the elements in the selection allows scaling """
- return np.any([target.do_scale for target in self.targets])
+ """if any of the elements in the selection allows scaling"""
+ return any([target.do_scale for target in self.targets])
def do_change_aspect_ratio(self) -> bool:
- """ if any of the element sin the selection wants to perserve its aspect ratio """
- return np.any([target.fixed_aspect for target in self.targets])
+ """if any of the element sin the selection wants to perserve its aspect ratio"""
+ return any([target.fixed_aspect for target in self.targets])
def width(self) -> float:
- """ the width of the current selection """
- return (self.p2-self.p1)[0]
+ """the width of the current selection"""
+ return (self.p2 - self.p1)[0]
def height(self) -> float:
- """ the height of the current selection """
- return (self.p2-self.p1)[1]
+ """the height of the current selection"""
+ return (self.p2 - self.p1)[1]
- def size(self) -> (float, float):
- """ the size of the current selection (width and height)"""
- return self.p2-self.p1
+ def size(self) -> Tuple[float, float]:
+ """the size of the current selection (width and height)"""
+ return self.p2 - self.p1
def get_trans_matrix(self):
- """ the transformation matrix for the current displacement and scaling of the selection """
+ """the transformation matrix for the current displacement and scaling of the selection"""
x, y = self.p1
w, h = self.size()
return np.array([[w, 0, x], [0, h, y], [0, 0, 1]], dtype=float)
def get_inv_trans_matrix(self):
- """ the inverse transformation for the current displacement and scaling of the selection """
+ """the inverse transformation for the current displacement and scaling of the selection"""
x, y = self.p1
w, h = self.size()
- return np.array([[1./w, 0, -x/w], [0, 1./h, -y/h], [0, 0, 1]], dtype=float)
+ return np.array(
+ [[1.0 / w, 0, -x / w], [0, 1.0 / h, -y / h], [0, 0, 1]], dtype=float
+ )
def transform(self, pos: Sequence) -> np.ndarray:
- """ apply the current transformation to a point """
+ """apply the current transformation to a point"""
return np.dot(self.get_trans_matrix(), [pos[0], pos[1], 1.0])
def inv_transform(self, pos: Sequence) -> np.ndarray:
- """ apply the inverse current transformation to a point """
+ """apply the inverse current transformation to a point"""
return np.dot(self.get_inv_trans_matrix(), [pos[0], pos[1], 1.0])
def get_pos(self, pos: Sequence) -> np.ndarray:
- """ transform a point """
+ """transform a point"""
return self.transform(pos)
- def get_save_point(self) -> callable:
- """ gather the current positions in a restore point for the undo function """
+ def get_save_point(self) -> Callable:
+ """gather the current positions in a restore point for the undo function"""
targets = [target.target for target in self.targets]
positions = [target.get_positions() for target in self.targets]
@@ -458,10 +554,11 @@ def undo():
target = TargetWrapper(target)
target.set_positions(pos)
self.add_target(target.target)
+
return undo
def start_move(self):
- """ start to move a grabber """
+ """start to move a grabber"""
self.start_p1 = self.p1.copy()
self.start_p2 = self.p2.copy()
self.hide_grabber()
@@ -470,47 +567,51 @@ def start_move(self):
self.store_start = self.get_save_point()
def end_move(self):
- """ a grabber move stopped """
+ """a grabber move stopped"""
self.update_grabber()
self.store_end = self.get_save_point()
if self.has_moved is True:
self.figure.signals.figure_selection_moved.emit()
- self.figure.change_tracker.addEdit([self.store_start, self.store_end, "Move"])
+ self.figure.change_tracker.addEdit(
+ [self.store_start, self.store_end, "Move"]
+ )
def addOffset(self, pos: Sequence, dir: int, keep_aspect_ratio: bool = True):
- """ move the whole selection (e.g. for the use of the arrow keys) """
+ """move the whole selection (e.g. for the use of the arrow keys)"""
pos = list(pos)
self.old_inv_transform = self.get_inv_trans_matrix()
- if (keep_aspect_ratio or self.do_change_aspect_ratio()) and not (dir & DIR_X0 and dir & DIR_X1 and dir & DIR_Y0 and dir & DIR_Y1):
+ if (keep_aspect_ratio or self.do_change_aspect_ratio()) and not (
+ dir & DIR_X0 and dir & DIR_X1 and dir & DIR_Y0 and dir & DIR_Y1
+ ):
if (dir & DIR_X0 and dir & DIR_Y0) or (dir & DIR_X1 and dir & DIR_Y1):
- dx = pos[1]*self.width()/self.height()
- dy = pos[0]*self.height()/self.width()
+ dx = pos[1] * self.width() / self.height()
+ dy = pos[0] * self.height() / self.width()
if abs(dx) < abs(dy):
pos[0] = dx
else:
pos[1] = dy
elif (dir & DIR_X0 and dir & DIR_Y1) or (dir & DIR_X1 and dir & DIR_Y0):
- dx = -pos[1]*self.width()/self.height()
- dy = -pos[0]*self.height()/self.width()
+ dx = -pos[1] * self.width() / self.height()
+ dy = -pos[0] * self.height() / self.width()
if abs(dx) < abs(dy):
pos[0] = dx
else:
pos[1] = dy
elif dir & DIR_X0 or dir & DIR_X1:
- dy = pos[0]*self.height()/self.width()
+ dy = pos[0] * self.height() / self.width()
if dir & DIR_X0:
- self.p1[1] = self.start_p1[1] + dy/2
- self.p2[1] = self.start_p2[1] - dy/2
+ self.p1[1] = self.start_p1[1] + dy / 2
+ self.p2[1] = self.start_p2[1] - dy / 2
else:
self.p1[1] = self.start_p1[1] - dy / 2
self.p2[1] = self.start_p2[1] + dy / 2
elif dir & DIR_Y0 or dir & DIR_Y1:
- dx = pos[1]*self.width()/self.height()
+ dx = pos[1] * self.width() / self.height()
if dir & DIR_Y0:
- self.p1[0] = self.start_p1[0] + dx/2
- self.p2[0] = self.start_p2[0] - dx/2
+ self.p1[0] = self.start_p1[0] + dx / 2
+ self.p2[0] = self.start_p2[0] - dx / 2
else:
self.p1[0] = self.start_p1[0] - dx / 2
self.p2[0] = self.start_p2[0] + dx / 2
@@ -529,68 +630,79 @@ def addOffset(self, pos: Sequence, dir: int, keep_aspect_ratio: bool = True):
self.transform_target(transform, target)
self.update_selection_rectangles(True)
- #for rect in self.targets_rects:
+ # for rect in self.targets_rects:
# self.transform_target(transform, TargetWrapper(rect))
- def move(self, pos: Sequence[float], dir: int, snaps: Sequence[SnapBase], keep_aspect_ratio: bool = False, ignore_snaps: bool = False):
- """ called from a grabber to move the selection. """
+ def move(
+ self,
+ pos: Sequence[float],
+ dir: int,
+ snaps: List[SnapBase],
+ keep_aspect_ratio: bool = False,
+ ignore_snaps: bool = False,
+ ):
+ """called from a grabber to move the selection."""
self.addOffset(pos, dir, keep_aspect_ratio)
self.has_moved = True
if not ignore_snaps:
offx, offy = checkSnaps(snaps)
- self.addOffset((pos[0]-offx, pos[1]-offy), dir, keep_aspect_ratio)
+ self.addOffset((pos[0] - offx, pos[1] - offy), dir, keep_aspect_ratio)
- offx, offy = checkSnaps(self.snaps)
+ checkSnaps(self.snaps)
checkSnapsActive(snaps)
def apply_transform(self, transform: np.ndarray, point: Sequence[float]):
- """ apply the given transformation to a point"""
+ """apply the given transformation to a point"""
point = np.array(point)
point = np.hstack((point, np.ones((point.shape[0], 1)))).T
return np.dot(transform, point)[:2].T
def transform_target(self, transform: np.ndarray, target: TargetWrapper):
- """ transform the position of an artist. """
+ """transform the position of an artist."""
points = target.get_positions()
points = self.apply_transform(transform, points)
target.set_positions(points)
def keyPressEvent(self, event: KeyEvent):
- """ when a key is pressed. Arrow keys move the selection, Pageup/down movein z """
- #if not self.selected:
+ """when a key is pressed. Arrow keys move the selection, Pageup/down movein z"""
+ # if not self.selected:
# return
# move last axis in z order
- if event.key == 'pagedown':
+ if event.key == "pagedown":
for target in self.targets:
target.target.set_zorder(target.target.get_zorder() - 1)
- self.figure.change_tracker.addChange(target.target, ".set_zorder(%d)" % target.target.get_zorder())
+ self.figure.change_tracker.addChange(
+ target.target, ".set_zorder(%d)" % target.target.get_zorder()
+ )
self.figure.canvas.draw()
- if event.key == 'pageup':
+ if event.key == "pageup":
for target in self.targets:
target.target.set_zorder(target.target.get_zorder() + 1)
- self.figure.change_tracker.addChange(target.target, ".set_zorder(%d)" % target.target.get_zorder())
+ self.figure.change_tracker.addChange(
+ target.target, ".set_zorder(%d)" % target.target.get_zorder()
+ )
self.figure.canvas.draw()
- if event.key == 'left':
+ if event.key == "left":
self.start_move()
self.addOffset((-1, 0), self.dir)
self.has_moved = True
self.end_move()
self.figure.canvas.schedule_draw()
- if event.key == 'right':
+ if event.key == "right":
self.start_move()
self.addOffset((+1, 0), self.dir)
self.has_moved = True
self.end_move()
self.figure.canvas.schedule_draw()
- if event.key == 'down':
+ if event.key == "down":
self.start_move()
self.addOffset((0, -1), self.dir)
self.has_moved = True
self.end_move()
self.figure.canvas.schedule_draw()
- if event.key == 'up':
+ if event.key == "up":
self.start_move()
self.addOffset((0, +1), self.dir)
self.has_moved = True
@@ -603,7 +715,8 @@ def keyPressEvent(self, event: KeyEvent):
class DragManager:
- """ a class to manage the selection and the moving of artists in a figure """
+ """a class to manage the selection and the moving of artists in a figure"""
+
selected_element = None
grab_element = None
@@ -611,53 +724,37 @@ def __init__(self, figure: Figure, no_save):
self.figure = figure
self.figure.figure_dragger = self
- self.figure.canvas.mpl_disconnect(self.figure.canvas.manager.key_press_handler_id)
+ manager = getattr(self.figure.canvas, "manager", None)
+ cid = getattr(manager, "key_press_handler_id", None)
+ if isinstance(cid, int):
+ self.figure.canvas.mpl_disconnect(cid)
self.activate()
- # make all the subplots pickable
- for index, axes in enumerate(self.figure.axes):
- axes.set_picker(True)
- leg = axes.get_legend()
- if leg:
- self.make_dragable(leg)
- for text in axes.texts:
- self.make_dragable(text)
- for attribute_name in ["title", "_left_title", "_right_title"]:
- text = getattr(axes, attribute_name, None)
- if text is not None:
- self.make_dragable(text)
- for patch in axes.patches:
- self.make_dragable(patch)
- self.make_dragable(axes.xaxis.get_label())
- self.make_dragable(axes.yaxis.get_label())
- self.make_dragable(axes)
-
- def make_figure_dragable(fig: Figure | SubFigure) -> None:
- for text in fig.texts:
- self.make_dragable(text)
- for patch in fig.patches:
- self.make_dragable(patch)
- for leg in fig.legends:
- self.make_dragable(leg)
-
- make_figure_dragable(self.figure)
- for subfig in self.figure.subfigs:
- make_figure_dragable(subfig)
-
- self.selection = GrabbableRectangleSelection(figure, figure._pyl_scene)
+ self.make_figure_draggable(self.figure)
+ self.make_axes_draggable(self.figure.axes)
+ graphics_scene = cast(
+ "GraphicsRectItemWithView", getattr(figure, "_pyl_scene", None)
+ )
+ self.selection = GrabbableRectangleSelection(figure, graphics_scene)
self.figure.selection = self.selection
self.change_tracker = ChangeTracker(figure, no_save)
self.figure.change_tracker = self.change_tracker
def activate(self):
- """ activate the interaction callbacks from the figure """
- self.c3 = self.figure.canvas.mpl_connect('button_release_event', self.button_release_event0)
- self.c2 = self.figure.canvas.mpl_connect('button_press_event', self.button_press_event0)
- self.c4 = self.figure.canvas.mpl_connect('key_press_event', self.key_press_event)
+ """activate the interaction callbacks from the figure"""
+ self.c3 = self.figure.canvas.mpl_connect(
+ "button_release_event", self.button_release_event0
+ )
+ self.c2 = self.figure.canvas.mpl_connect(
+ "button_press_event", self.button_press_event0
+ )
+ self.c4 = self.figure.canvas.mpl_connect(
+ "key_press_event", self.key_press_event
+ )
def deactivate(self):
- """ deactivate the interaction callbacks from the figure """
+ """deactivate the interaction callbacks from the figure"""
self.figure.canvas.mpl_disconnect(self.c3)
self.figure.canvas.mpl_disconnect(self.c2)
self.figure.canvas.mpl_disconnect(self.c4)
@@ -667,40 +764,93 @@ def deactivate(self):
self.on_select(None, None)
self.figure.canvas.draw()
- def make_dragable(self, target: Artist):
- """ make an artist draggable """
+ def make_draggable(self, target: Artist):
+ """make an artist draggable"""
target.set_picker(True)
if isinstance(target, Text):
target.set_bbox(dict(facecolor="none", edgecolor="none"))
- def get_picked_element(self, event: MouseEvent, element: Artist = None, picked_element: Artist = None, last_selected: Artist = None):
- """ get the picked element that an event refers to.
+ def make_axes_draggable(self, axes: list[Axes]) -> None:
+ for index, ax in enumerate(axes):
+ ax.set_picker(True)
+ leg = ax.get_legend()
+ if leg:
+ self.make_draggable(leg)
+ for text in ax.texts:
+ self.make_draggable(text)
+ for attribute_name in ["title", "_left_title", "_right_title"]:
+ text = getattr(ax, attribute_name, None)
+ if text is not None:
+ self.make_draggable(text)
+ for patch in ax.patches:
+ self.make_draggable(patch)
+ self.make_draggable(ax.xaxis.get_label())
+ self.make_draggable(ax.yaxis.get_label())
+ self.make_draggable(ax)
+ self.make_axes_draggable([a for a in ax.child_axes if isinstance(a, Axes)])
+
+ def make_figure_draggable(self, fig: Figure | SubFigure) -> None:
+ for text in fig.texts:
+ self.make_draggable(text)
+ for patch in fig.patches:
+ self.make_draggable(patch)
+ for leg in fig.legends:
+ self.make_draggable(leg)
+ for subfig in fig.subfigs:
+ self.make_figure_draggable(subfig)
+
+ def get_picked_element(
+ self,
+ event: MouseEvent,
+ element: Artist | Figure | None = None,
+ picked_element: Artist | None = None,
+ last_selected: Artist | None = None,
+ ):
+ """get the picked element that an event refers to.
To implement selection of elements at the back with multiple clicks.
"""
- # start with the figure
- if element is None:
+ if not isinstance(element, (Artist, Figure)):
element = self.figure
+ if not isinstance(element, (Artist, Figure)):
+ raise ValueError("element must be an Artist or Figure")
+
finished = False
# iterate over all children
- for child in sorted(element.get_children(), key=lambda x: x.get_zorder()):
+ for child in sorted(
+ cast(Artist, element).get_children(), key=lambda x: x.get_zorder()
+ ):
# check if the element is contained in the event and has an active dragger
- #if child.contains(event)[0] and ((getattr(child, "_draggable", None) and getattr(child, "_draggable",
+ # if child.contains(event)[0] and ((getattr(child, "_draggable", None) and getattr(child, "_draggable",
# None).connected) or isinstance(child, GrabberGeneric) or isinstance(child, GrabbableRectangleSelection)):
- if child.get_visible() and child.contains(event)[0] and (child.pickable() or isinstance(child, GrabberGeneric)) and not (child.get_label() is not None and child.get_label().startswith("_")):
+ child_label = child.get_label()
+ is_underscored = (
+ child_label is not None
+ and isinstance(child_label, str)
+ and child_label.startswith("_")
+ )
+ if (
+ child.get_visible()
+ and child.contains(event)[0]
+ and (child.pickable() or isinstance(child, GrabberGeneric))
+ and not is_underscored
+ ):
# if the element is the last selected, finish the search
if child == last_selected:
return picked_element, True
# use this element as the current best matching element
picked_element = child
# iterate over the children's children
- picked_element, finished = self.get_picked_element(event, child, picked_element, last_selected=last_selected)
+ picked_element, finished = self.get_picked_element(
+ event, child, picked_element, last_selected=last_selected
+ )
# if the subcall wants to finish, just break the loop
if finished:
break
return picked_element, finished
- def button_release_event0(self, event: MouseEvent):
- """ when the mouse button is released """
+ def button_release_event0(self, event: Event):
+ """when the mouse button is released"""
+ event = cast(MouseEvent, event)
# release the grabber
if self.grab_element:
self.grab_element.button_release_event(event)
@@ -709,14 +859,22 @@ def button_release_event0(self, event: MouseEvent):
elif len(self.selection.targets):
self.selection.button_release_event(event)
- def button_press_event0(self, event: MouseEvent):
- """ when the mouse button is pressed """
+ def button_press_event0(self, event: Event):
+ """when the mouse button is pressed"""
+ event = cast(MouseEvent, event)
if event.button == 1:
last = self.selection.targets[-1] if len(self.selection.targets) else None
- contained = np.any([t.target.contains(event)[0] for t in self.selection.targets])
+ contained = np.any(
+ [t.target.contains(event)[0] for t in self.selection.targets]
+ )
# recursively iterate over all elements
- picked_element, _ = self.get_picked_element(event, last_selected=last if event.dblclick else None)
+ picked_element, _ = self.get_picked_element(
+ event,
+ last_selected=(
+ last.target if (event.dblclick and last is not None) else None
+ ),
+ )
# if the element is a grabber, store it
if isinstance(picked_element, GrabberGeneric):
@@ -733,8 +891,8 @@ def button_press_event0(self, event: MouseEvent):
elif contained:
self.selection.button_press_event(event)
- def select_element(self, element: Artist, event: MouseEvent = None):
- """ select an artist in a figure """
+ def select_element(self, element: Artist, event: MouseEvent | None = None):
+ """select an artist in a figure"""
# do nothing if it is already selected
if element == self.selected_element:
return
@@ -746,15 +904,19 @@ def select_element(self, element: Artist, event: MouseEvent = None):
self.on_select(element, event)
self.selected_element = element
- def on_deselect(self, event: MouseEvent):
- """ deselect currently selected artists"""
- modifier = "shift" in event.key.split("+") if event is not None and event.key is not None else False
+ def on_deselect(self, event: MouseEvent | None):
+ """deselect currently selected artists"""
+ modifier = (
+ "shift" in event.key.split("+")
+ if event is not None and event.key is not None
+ else False
+ )
# only if the modifier key is not used
if not modifier:
self.selection.clear_targets()
- def on_select(self, element: Artist, event: MouseEvent):
- """ when an artist is selected """
+ def on_select(self, element: Artist | None, event: MouseEvent | None):
+ """when an artist is selected"""
if element is not None:
self.selection.add_target(element)
@@ -774,10 +936,11 @@ def redo(self):
self.on_select(None, None)
self.figure.canvas.draw()
- def key_press_event(self, event: KeyEvent):
- """ when a key is pressed """
+ def key_press_event(self, event: Event):
+ """when a key is pressed"""
+ event = cast(KeyEvent, event)
# space: print code to restore current configuration
- if event.key == 'ctrl+s':
+ if event.key == "ctrl+s":
self.figure.change_tracker.save()
if event.key == "ctrl+z":
self.undo()
@@ -791,10 +954,18 @@ def key_press_event(self, event: KeyEvent):
class GrabberGeneric(GrabFunctions):
- """ a generic grabber object to move a selection """
+ """a generic grabber object to move a selection"""
+
_no_save = True
- def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir: int):
+ def __init__(
+ self,
+ parent: "GrabbableRectangleSelection",
+ x: float,
+ y: float,
+ dir: int,
+ scene: Any | None = None,
+ ):
self._animated = True
GrabFunctions.__init__(self, parent, dir)
self.pos = (x, y)
@@ -803,26 +974,34 @@ def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir:
def get_xy(self):
return self.center
- def set_xy(self, xy: (float, float)):
+ def set_xy(self, xy: tuple[float, float]):
self.center = xy
def getPos(self):
x, y = self.get_xy()
- return self.transform.transform((x, y))
+ t = getattr(self, "transform", None)
+ if t is None:
+ return x, y
+ return t.transform((x, y))
def updatePos(self):
self.set_xy(self.parent.get_pos(self.pos))
- def applyOffset(self, pos: (float, float), event: MouseEvent):
- self.set_xy((self.ox+pos[0], self.oy+pos[1]))
-
class GrabberGenericRound(GrabberGeneric):
- """ a rectangle with a round appearance """
+ """a rectangle with a round appearance"""
+
d = 10
shape = "round"
- def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir: int, scene):
+ def __init__(
+ self,
+ parent: GrabbableRectangleSelection,
+ x: float,
+ y: float,
+ dir: int,
+ scene: "GraphicsRectItemWithView",
+ ):
pen3 = QtGui.QPen(QtGui.QColor("black"), 2)
brush1 = QtGui.QBrush(QtGui.QColor("red"))
@@ -835,37 +1014,46 @@ def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir:
GrabberGeneric.__init__(self, parent, x, y, dir)
- def set_xy(self, xy: (float, float)):
+ def set_xy(self, xy: tuple[float, float]):
self.xy = xy
- self.ellipse.setRect(xy[0]-5, xy[1]-5, 10, 10)
+ self.ellipse.setRect(xy[0] - 5, xy[1] - 5, 10, 10)
+ self.ellipse.setRect(xy[0] - 5, xy[1] - 5, 10, 10)
class GrabberGenericRectangle(GrabberGeneric):
- """ a rectangle with a square appearance """
+ """a rectangle with a square appearance"""
+
d = 10
shape = "rect"
- def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir: int, scene):
+ def __init__(
+ self,
+ parent: GrabbableRectangleSelection,
+ x: float,
+ y: float,
+ dir: int,
+ scene: "GraphicsRectItemWithView",
+ ):
# somehow the original "self" rectangle does not show up in the current matplotlib version, therefore this doubling
- #self.rect = Rectangle((0, 0), self.d, self.d, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
- #self.rect._no_save = True
- #parent.figure.patches.append(self.rect)
+ # self.rect = Rectangle((0, 0), self.d, self.d, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
+ # self.rect._no_save = True
+ # parent.figure.patches.append(self.rect)
- #Rectangle.__init__(self, (0, 0), self.d, self.d, picker=True, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
+ # Rectangle.__init__(self, (0, 0), self.d, self.d, picker=True, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
- #self.figure.patches.append(self)
+ # self.figure.patches.append(self)
pen3 = QtGui.QPen(QtGui.QColor("black"), 2)
brush1 = QtGui.QBrush(QtGui.QColor("red"))
- self.ellipse = MyRect(x-5, y-5, 10, 10, scene)
+ self.ellipse = MyRect(x - 5, y - 5, 10, 10, scene)
self.ellipse.view = scene.view
self.ellipse.grabber = self
self.ellipse.setPen(pen3)
self.ellipse.setBrush(brush1)
self.xy = (x, y)
- #self.updatePos()
+ # self.updatePos()
GrabberGeneric.__init__(self, parent, x, y, dir)
@@ -874,7 +1062,7 @@ def get_xy(self):
xy = Rectangle.get_xy(self)
return xy[0] + self.d / 2, xy[1] + self.d / 2
- def set_xy(self, xy: (float, float)):
+ def set_xy(self, xy: Tuple[float, float]):
self.xy = xy
self.ellipse.setRect(xy[0] - 5, xy[1] - 5, 10, 10)
@@ -882,32 +1070,45 @@ def set_xy(self, xy: (float, float)):
Rectangle.set_xy(self, (xy[0] - self.d / 2, xy[1] - self.d / 2))
self.rect.set_xy((xy[0] - self.d / 2, xy[1] - self.d / 2))
+
class MyItem:
w = 10
+ view: Any
+ grabber: Any
+ scene: Callable[[], Any]
def mousePressEvent(self, e):
- super().mousePressEvent(e)
- self.view.grabber_found = True
+ cast(Any, super()).mousePressEvent(e)
+ cast(Any, self).view.grabber_found = True
p = e.scenePos()
self.scene().grabber_pressed = self
- self.grabber.button_press_event(MyEvent(p.x(), self.view.h - p.y()))
+ cast(Any, self).grabber.button_press_event(
+ MyEvent(p.x(), cast(Any, self).view.h - p.y())
+ )
def mouseReleaseEvent(self, e):
- super().mouseReleaseEvent(e)
+ cast(Any, super()).mouseReleaseEvent(e)
self.scene().grabber_pressed = None
- self.view.grabber_found = True
+ cast(Any, self).view.grabber_found = True
p = e.scenePos()
- self.grabber.button_release_event(MyEvent(p.x(), self.view.h - p.y()))
+ cast(Any, self).grabber.button_release_event(
+ MyEvent(p.x(), cast(Any, self).view.h - p.y())
+ )
+
class MyRect(MyItem, QtWidgets.QGraphicsRectItem):
+ view = None
+ grabber = None
pass
+
class MyEllipse(MyItem, QtWidgets.QGraphicsEllipseItem):
+ view = None
+ grabber = None
pass
-
class MyEvent:
def __init__(self, x, y):
self.x = x
- self.y = y
\ No newline at end of file
+ self.y = y
diff --git a/pylustrator/exception_swallower.py b/pylustrator/exception_swallower.py
index e4a82de..4de316e 100644
--- a/pylustrator/exception_swallower.py
+++ b/pylustrator/exception_swallower.py
@@ -25,7 +25,8 @@
class Dummy:
- """ a dummy object that provides dummy attributes, dummy items and dummy returns """
+ """a dummy object that provides dummy attributes, dummy items and dummy returns"""
+
def __getattr__(self, item):
return Dummy()
@@ -37,7 +38,8 @@ def __getitem__(self, item):
class SaveList(list):
- """ a list that returns dummy objects when an invalid item is requested """
+ """a list that returns dummy objects when an invalid item is requested"""
+
def __init__(self, target):
list.__init__(self, target)
@@ -49,7 +51,8 @@ def __getitem__(self, item):
class SaveDict(dict):
- """ a dictionary that returns dummy objects when an invalid item is requested """
+ """a dictionary that returns dummy objects when an invalid item is requested"""
+
def __init__(self, target):
dict.__init__(self, target)
@@ -61,9 +64,10 @@ def __getitem__(self, item):
class SaveTuple(tuple):
- """ a tuple that returns dummy objects when an invalid item is requested """
+ """a tuple that returns dummy objects when an invalid item is requested"""
+
def __init__(self, target):
- tuple.__init__(self, target)
+ tuple.__init__(self, target) # ty:ignore[too-many-positional-arguments]
def __getitem__(self, item):
try:
@@ -73,7 +77,8 @@ def __getitem__(self, item):
class SaveListDescriptor:
- """ a descriptor that wraps the target value with a SaveList, SaveDict or SaveTuple """
+ """a descriptor that wraps the target value with a SaveList, SaveDict or SaveTuple"""
+
def __init__(self, variable_name):
self.variable_name = variable_name
@@ -84,11 +89,11 @@ def __set__(self, instance, value):
value = SaveDict(value)
if isinstance(value, tuple):
value = SaveTuple(value)
- setattr(instance, "_pylustrator_"+self.variable_name, value)
+ setattr(instance, "_pylustrator_" + self.variable_name, value)
def __get__(self, instance, owner):
try:
- return getattr(instance, "_pylustrator_"+self.variable_name)
+ return getattr(instance, "_pylustrator_" + self.variable_name)
except AttributeError:
if self.variable_name in instance.__dict__:
return instance.__dict__[self.variable_name]
@@ -97,35 +102,38 @@ def __get__(self, instance, owner):
def get_axes(self):
- """ a function that returns the axes of a figure as a SaveList """
+ """a function that returns the axes of a figure as a SaveList"""
return SaveList(self._axstack.as_list())
def return_save_list(func):
- """ a decorator to wrap the output of a function as a SaveList """
+ """a decorator to wrap the output of a function as a SaveList"""
+
def wrap(*args, **kwargs):
return SaveList(func(*args, **kwargs))
+
return wrap
def swallow_get_exceptions():
- """ replace lists with lists that return dummy objects when items are not present.
- this is to ensure that the pylustrator generated code does not fail, even if the user removes some elements
- from the figure.
+ """replace lists with lists that return dummy objects when items are not present.
+ this is to ensure that the pylustrator generated code does not fail, even if the user removes some elements
+ from the figure.
"""
- Figure._get_axes = get_axes
- Figure.axes = property(fget=get_axes)
- Figure.ax_dict = SaveListDescriptor("ax_dict")
- _AxesBase.texts = SaveListDescriptor("texts")
- _AxesBase.lines = SaveListDescriptor("lines")
- _AxesBase.patches = SaveListDescriptor("patches")
+ Figure._get_axes = get_axes # ty:ignore[unresolved-attribute]
+ Figure.axes = property(fget=get_axes) # ty:ignore[invalid-assignment]
+ Figure.ax_dict = SaveListDescriptor("ax_dict") # ty:ignore[unresolved-attribute]
+ _AxesBase.texts = SaveListDescriptor("texts") # ty:ignore[invalid-assignment]
+ _AxesBase.lines = SaveListDescriptor("lines") # ty:ignore[invalid-assignment]
+ _AxesBase.patches = SaveListDescriptor("patches") # ty:ignore[invalid-assignment]
Axis.get_minor_ticks = return_save_list(Axis.get_minor_ticks)
Axis.get_major_ticks = return_save_list(Axis.get_major_ticks)
- l = _AxesBase.get_legend
+ get_legend_orig = _AxesBase.get_legend
+
def get_legend(*args, **kwargs):
- leg = l(*args, **kwargs)
+ leg = get_legend_orig(*args, **kwargs)
if leg is None:
return Dummy()
return leg
- _AxesBase.get_legend = get_legend
\ No newline at end of file
+ _AxesBase.get_legend = get_legend # ty:ignore[invalid-assignment]
diff --git a/pylustrator/helper_functions.py b/pylustrator/helper_functions.py
index a54a063..05449c8 100644
--- a/pylustrator/helper_functions.py
+++ b/pylustrator/helper_functions.py
@@ -21,14 +21,13 @@
from __future__ import division
import matplotlib.pyplot as plt
-from matplotlib.text import Text
import numpy as np
-import traceback
from .parse_svg import svgread
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
- from matplotlib.axes._subplots import Axes
+except ImportError:
+ from matplotlib.axes._subplots import Axes # ty:ignore[unresolved-import]
from matplotlib.figure import Figure
from .pyjack import replace_all_refs
import os
@@ -65,19 +64,25 @@ def add_axes(dim: Sequence, unit: str = "cm", *args, **kwargs):
x += 1
if y < 0:
y += 1
- return plt.axes([x, y, w, h], *args, **kwargs)
+ return plt.axes((x, y, w, h), *args, **kwargs)
def add_image(filename: str):
- """ add an image to the current axes """
+ """add an image to the current axes"""
plt.imshow(plt.imread(filename))
plt.xticks([])
plt.yticks([])
-def changeFigureSize(w: float, h: float, cut_from_top: bool = False, cut_from_left: bool = False, fig: Figure = None):
- """ change the figure size to the given dimensions. Optionally define if to remove or add space at the top or bottom
- and left or right.
+def changeFigureSize(
+ w: float,
+ h: float,
+ cut_from_top: bool = False,
+ cut_from_left: bool = False,
+ fig: Figure | None = None,
+):
+ """change the figure size to the given dimensions. Optionally define if to remove or add space at the top or bottom
+ and left or right.
"""
if fig is None:
fig = plt.gcf()
@@ -88,32 +93,59 @@ def changeFigureSize(w: float, h: float, cut_from_top: bool = False, cut_from_le
box = axe.get_position()
if cut_from_top:
if cut_from_left:
- axe.set_position([1 - (1 - box.x0) * fx, box.y0 * fy, (box.x1 - box.x0) * fx, (box.y1 - box.y0) * fy])
+ axe.set_position(
+ (
+ 1 - (1 - box.x0) * fx,
+ box.y0 * fy,
+ (box.x1 - box.x0) * fx,
+ (box.y1 - box.y0) * fy,
+ )
+ )
else:
- axe.set_position([box.x0 * fx, box.y0 * fy, (box.x1 - box.x0) * fx, (box.y1 - box.y0) * fy])
+ axe.set_position(
+ (
+ box.x0 * fx,
+ box.y0 * fy,
+ (box.x1 - box.x0) * fx,
+ (box.y1 - box.y0) * fy,
+ )
+ )
else:
if cut_from_left:
axe.set_position(
- [1 - (1 - box.x0) * fx, 1 - (1 - box.y0) * fy, (box.x1 - box.x0) * fx, (box.y1 - box.y0) * fy])
+ (
+ 1 - (1 - box.x0) * fx,
+ 1 - (1 - box.y0) * fy,
+ (box.x1 - box.x0) * fx,
+ (box.y1 - box.y0) * fy,
+ )
+ )
else:
- axe.set_position([box.x0 * fx, 1 - (1 - box.y0) * fy, (box.x1 - box.x0) * fx, (box.y1 - box.y0) * fy])
+ axe.set_position(
+ (
+ box.x0 * fx,
+ 1 - (1 - box.y0) * fy,
+ (box.x1 - box.x0) * fx,
+ (box.y1 - box.y0) * fy,
+ )
+ )
for text in fig.texts:
x0, y0 = text.get_position()
if cut_from_top:
if cut_from_left:
- text.set_position([1 - (1- x0) * fx, y0 * fy])
+ text.set_position((1 - (1 - x0) * fx, y0 * fy))
else:
- text.set_position([x0 * fx, y0 * fy])
+ text.set_position((x0 * fx, y0 * fy))
else:
if cut_from_left:
- text.set_position([1 - (1 - x0) * fx, 1 - (1 - y0) * fy])
+ text.set_position((1 - (1 - x0) * fx, 1 - (1 - y0) * fy))
else:
- text.set_position([x0 * fx, 1 - (1 - y0) * fy])
+ text.set_position((x0 * fx, 1 - (1 - y0) * fy))
fig.set_size_inches(w, h, forward=True)
def removeContentFromFigure(fig: Figure):
- """ remove axes and text from a figure """
+ """remove axes and text from a figure"""
axes = []
for ax in fig._axstack.as_list():
axes.append(ax)
@@ -124,7 +156,7 @@ def removeContentFromFigure(fig: Figure):
def addContentToFigure(fig: Figure, axes: Sequence):
- """ add axes and texts to a figure """
+ """add axes and texts to a figure"""
index = len(fig._axstack.as_list())
for ax in axes:
if isinstance(ax, Axes):
@@ -154,13 +186,20 @@ def get_unique_label(fig1, label_base):
return label
-def imShowFullFigure(im: np.ndarray, filename: str, fig1: Figure, dpi: int, label: str):
- """ create a new axes and display an image in this axes """
+def imShowFullFigure(
+ im: np.ndarray,
+ filename: str,
+ fig1: Figure,
+ dpi: int | None,
+ label: str | None = None,
+):
+ """create a new axes and display an image in this axes"""
from matplotlib import rcParams
+
if dpi is None:
- dpi = rcParams['figure.dpi']
+ dpi = rcParams["figure.dpi"]
fig1.set_size_inches(im.shape[1] / dpi, im.shape[0] / dpi)
- ax = plt.axes([0, 0, 1, 1], label=label)
+ ax = plt.axes((0, 0, 1, 1), label=label)
plt.imshow(im, cmap="gray")
plt.xticks([])
plt.yticks([])
@@ -172,6 +211,7 @@ class changeFolder:
"""
An environment that changes the working directory
"""
+
def __init__(self, directory):
self.directory = directory
@@ -184,7 +224,14 @@ def __exit__(self, type, value, traceback):
os.chdir(self.old_dir)
-def loadFigureFromFile(filename: str, figure: Figure = None, offset: list = None, dpi: int = None, cache: bool = False, label: str = ""):
+def loadFigureFromFile(
+ filename: str,
+ figure: Figure | None = None,
+ offset: list | None = None,
+ dpi: int | None = None,
+ cache: bool = False,
+ label: str = "",
+):
"""
Add contents to the current figure from the file defined by filename. It can be either a python script defining
a figure, an image (filename or directly the numpy array), or an svg file.
@@ -226,6 +273,7 @@ class noShow:
"""
An environment that prevents the script from calling the plt.show function
"""
+
def __enter__(self):
# store the show function
self.show = plt.show
@@ -236,8 +284,8 @@ def empty(*args, **kwargs):
pass
# set the show function to the empty function
- plt.show = empty
- pylustrator.start = empty
+ plt.show = empty # ty:ignore[invalid-assignment]
+ pylustrator.start = empty # ty:ignore[invalid-assignment]
def __exit__(self, type, value, traceback):
# restore the old show function
@@ -248,21 +296,22 @@ class noNewFigures:
"""
An environment that prevents the script from creating new figures in the figure manager
"""
+
def __enter__(self):
fig = plt.gcf()
self.fig = plt.figure
- figsize = rcParams['figure.figsize']
+ figsize = rcParams["figure.figsize"]
fig.set_size_inches(figsize[0], figsize[1])
+
def figure(num=None, figsize=None, *args, **kwargs):
fig = plt.gcf()
if figsize is not None:
fig.set_size_inches(figsize[0], figsize[1], forward=True)
return fig
- plt.figure = figure
+
+ plt.figure = figure # ty:ignore[invalid-assignment]
def __exit__(self, type, value, traceback):
- from matplotlib.figure import Figure
- from matplotlib.transforms import TransformedBbox, Affine2D
plt.figure = self.fig
# get the size of the old figure
@@ -281,7 +330,9 @@ def __exit__(self, type, value, traceback):
# if it is an image, just display the image
if im is not None:
im = plt.imread(filename)
- imShowFullFigure(im, os.path.split(filename)[1], figure, dpi=dpi, label=label)
+ imShowFullFigure(
+ im, os.path.split(filename)[1], figure, dpi=dpi, label=label
+ )
# if the image is a numpy array, just display the array
elif isinstance(filename, np.ndarray):
im = filename
@@ -298,7 +349,13 @@ def __exit__(self, type, value, traceback):
# prevent the script we want to load from calling show
with noShow():
import pickle
- if cache and os.path.exists(cache_filename) and os.path.getmtime(cache_filename) > os.path.getmtime(filename):
+
+ if (
+ cache
+ and os.path.exists(cache_filename)
+ and os.path.getmtime(cache_filename)
+ > os.path.getmtime(filename)
+ ):
print("loading from cached file", cache_filename)
fig2 = pickle.load(open(cache_filename, "rb"))
w, h = fig2.get_size_inches()
@@ -309,19 +366,27 @@ def __exit__(self, type, value, traceback):
fig2.delaxes(ax)
figure._axstack.add(figure._make_key(ax), ax)
figure.bbox._parents.update(fig2.bbox._parents)
- figure.dpi_scale_trans._parents.update(fig2.dpi_scale_trans._parents)
+ figure.dpi_scale_trans._parents.update(
+ fig2.dpi_scale_trans._parents
+ )
replace_all_refs(fig2.bbox, figure.bbox)
- replace_all_refs(fig2.dpi_scale_trans, figure.dpi_scale_trans)
+ replace_all_refs(
+ fig2.dpi_scale_trans,
+ figure.dpi_scale_trans,
+ )
replace_all_refs(fig2, figure)
else:
# execute the file
- exec(compile(open(filename, "rb").read(), filename, 'exec'), globals())
+ exec(
+ compile(open(filename, "rb").read(), filename, "exec"),
+ globals(),
+ )
if cache is True:
c = figure.canvas
figure.canvas = None
figure.bbox.pylustrator = True
figure.dpi_scale_trans.pylustrator = True
- pickle.dump(figure, open(cache_filename, 'wb'))
+ pickle.dump(figure, open(cache_filename, "wb"))
figure.canvas = c
@@ -352,18 +417,17 @@ def __exit__(self, type, value, traceback):
# helper_functions.py
def convertFromPyplot(old, new):
-
w, h = old.get_size_inches()
new.set_size_inches(w, h)
str(new) # important! (for some reason I don't know)
for ax in old.axes:
- #old.delaxes(ax)
+ # old.delaxes(ax)
ax.remove()
ax.figure = new
new.axes.append(ax)
new.add_axes(ax)
- #new._axstack.add(new._make_key(ax), ax)
+ # new._axstack.add(new._make_key(ax), ax)
new.bbox._parents.update(old.bbox._parents)
new.dpi_scale_trans._parents.update(old.dpi_scale_trans._parents)
replace_all_refs(old.bbox, new.bbox)
@@ -372,19 +436,32 @@ def convertFromPyplot(old, new):
replace_all_refs(old, new)
-def mark_inset(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequence[int]] = 1, loc2: Union[int, Sequence[int]] = 2, **kwargs):
- """ like the mark_inset function from matplotlib, but loc can also be a tuple """
- from mpl_toolkits.axes_grid1.inset_locator import TransformedBbox, BboxPatch, BboxConnector
- try:
- loc1a, loc1b = loc1
- except:
+def mark_inset(
+ parent_axes: Axes,
+ inset_axes: Axes,
+ loc1: Union[int, Sequence[int]] = 1,
+ loc2: Union[int, Sequence[int]] = 2,
+ **kwargs,
+):
+ """like the mark_inset function from matplotlib, but loc can also be a tuple"""
+ from mpl_toolkits.axes_grid1.inset_locator import (
+ TransformedBbox,
+ BboxPatch,
+ BboxConnector,
+ )
+
+ if isinstance(loc1, int):
loc1a = loc1
loc1b = loc1
- try:
- loc2a, loc2b = loc2
- except:
+ else:
+ loc1a, loc1b = loc1
+
+ if isinstance(loc2, int):
loc2a = loc2
loc2b = loc2
+ else:
+ loc2a, loc2b = loc2
+
rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData)
pp = BboxPatch(rect, fill=False, **kwargs)
@@ -401,9 +478,16 @@ def mark_inset(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequence[in
return pp, p1, p2
-def draw_from_point_to_bbox(parent_axes: Axes, insert_axes: Axes, point: Sequence, loc=1, **kwargs):
- """ add a box connector from a point to an axes """
- from mpl_toolkits.axes_grid1.inset_locator import TransformedBbox, BboxConnector, Bbox
+def draw_from_point_to_bbox(
+ parent_axes: Axes, insert_axes: Axes, point: Sequence, loc=1, **kwargs
+):
+ """add a box connector from a point to an axes"""
+ from mpl_toolkits.axes_grid1.inset_locator import (
+ TransformedBbox,
+ BboxConnector,
+ Bbox,
+ )
+
rect = TransformedBbox(Bbox([point, point]), parent_axes.transData)
# rect = TransformedBbox(Bbox([[1, 0], [1, 0]]), parent_axes.transData)
p1 = BboxConnector(rect, insert_axes.bbox, loc, **kwargs)
@@ -412,9 +496,16 @@ def draw_from_point_to_bbox(parent_axes: Axes, insert_axes: Axes, point: Sequenc
return p1
-def draw_from_point_to_point(parent_axes: Axes, insert_axes: Axes, point1: Sequence, point2: Sequence, **kwargs):
- """ add a box connector from a point in on axes to a point in another axes """
- from mpl_toolkits.axes_grid1.inset_locator import TransformedBbox, BboxConnector, Bbox
+def draw_from_point_to_point(
+ parent_axes: Axes, insert_axes: Axes, point1: Sequence, point2: Sequence, **kwargs
+):
+ """add a box connector from a point in on axes to a point in another axes"""
+ from mpl_toolkits.axes_grid1.inset_locator import (
+ TransformedBbox,
+ BboxConnector,
+ Bbox,
+ )
+
rect = TransformedBbox(Bbox([point1, point1]), parent_axes.transData)
rect2 = TransformedBbox(Bbox([point2, point2]), insert_axes.transData)
# rect = TransformedBbox(Bbox([[1, 0], [1, 0]]), parent_axes.transData)
@@ -425,10 +516,17 @@ def draw_from_point_to_point(parent_axes: Axes, insert_axes: Axes, point1: Seque
return p1
-def mark_inset_pos(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequence[int]], loc2: Union[int, Sequence[int]], point: Sequence, **kwargs):
- """ add a box connector where the second axis is shrinked to a point """
+def mark_inset_pos(
+ parent_axes: Axes,
+ inset_axes: Axes,
+ loc1: Union[int, Sequence[int]],
+ loc2: Union[int, Sequence[int]],
+ point: Sequence,
+ **kwargs,
+):
+ """add a box connector where the second axis is shrunk to a point"""
kwargs["lw"] = 0.8
- ax_new = plt.axes(inset_axes.get_position())
+ ax_new = plt.axes(inset_axes.get_position().bounds)
ax_new.set_xlim(point[0], point[0])
ax_new.set_ylim(point[1], point[1])
mark_inset(parent_axes, ax_new, loc1, loc2, **kwargs)
@@ -437,15 +535,21 @@ def mark_inset_pos(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequenc
ax_new.set_zorder(inset_axes.get_zorder() - 1)
-def VoronoiPlot(points: Sequence, values: Sequence, vmin: float = None, vmax:float = None, cmap=None):
- """ plot the voronoi regions of the poins with the given colormap """
+def VoronoiPlot(
+ points: Sequence,
+ values: Sequence,
+ vmin: float | None = None,
+ vmax: float | None = None,
+ cmap=None,
+):
+ """plot the voronoi regions of the poins with the given colormap"""
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
- from scipy.spatial import Voronoi, voronoi_plot_2d
+ from scipy.spatial import Voronoi
from matplotlib import cm
if cmap is None:
- cmap = cm.get_cmap('viridis')
+ cmap = cm.get_cmap("viridis")
vor = Voronoi(points)
@@ -466,14 +570,14 @@ def VoronoiPlot(points: Sequence, values: Sequence, vmin: float = None, vmax:flo
excluded_indices.append(index)
continue
region = np.array([vor.vertices[i] for i in reg])
- polygon = Polygon(region, True)
+ polygon = Polygon(region, True) # ty:ignore[too-many-positional-arguments]
patches.append(polygon)
dists = values[index]
dist_list.append(dists)
# plt.plot(p[0], p[1], 'ok', alpha=0.3, ms=1)
p = PatchCollection(patches, cmap=cmap)
- p.set_clim([vmin, vmax])
+ p.set_clim((vmin, vmax)) # ty:ignore[invalid-argument-type] - matplotlib accepts None for auto-calculation
p.set_array(np.array(dist_list))
p.set_linewidth(10)
@@ -484,42 +588,49 @@ def VoronoiPlot(points: Sequence, values: Sequence, vmin: float = None, vmax:flo
def selectRectangle(axes: Axes = None):
- """ add a rectangle selector to the given axes """
+ """add a rectangle selector to the given axes"""
if axes is None:
axes = plt.gca()
def onselect(eclick, erelease):
- 'eclick and erelease are matplotlib events at press and release'
- print(' startposition : (%f, %f)' % (eclick.xdata, eclick.ydata))
- print(' endposition : (%f, %f)' % (erelease.xdata, erelease.ydata))
- print(' used button : ', eclick.button)
+ "eclick and erelease are matplotlib events at press and release"
+ print(" startposition : (%f, %f)" % (eclick.xdata, eclick.ydata))
+ print(" endposition : (%f, %f)" % (erelease.xdata, erelease.ydata))
+ print(" used button : ", eclick.button)
from matplotlib.widgets import RectangleSelector
+
rect_selector = RectangleSelector(axes, onselect)
return rect_selector
def despine(ax: Axes = None, complete: bool = False):
- """ despine the given axes """
+ """despine the given axes"""
if not ax:
ax = plt.gca()
- ax.spines['right'].set_visible(False)
- ax.spines['top'].set_visible(False)
+ ax.spines["right"].set_visible(False)
+ ax.spines["top"].set_visible(False)
if complete:
- ax.spines['left'].set_visible(False)
- ax.spines['bottom'].set_visible(False)
+ ax.spines["left"].set_visible(False)
+ ax.spines["bottom"].set_visible(False)
ax.set_xticks([])
ax.set_yticks([])
else:
# Only show ticks on the left and bottom spines
- ax.yaxis.set_ticks_position('left')
- ax.xaxis.set_ticks_position('bottom')
-
+ ax.yaxis.set_ticks_position("left")
+ ax.xaxis.set_ticks_position("bottom")
letter_index = 0
-def add_letter(ax: Axes = None, offset: float = 0, offset2: float = 0, letter: str = None):
- """ add a letter indicating which subplot it is to the given figure """
+
+
+def add_letter(
+ ax: Axes = None,
+ offset: float = 0,
+ offset2: float = 0,
+ letter: str | None = None,
+):
+ """add a letter indicating which subplot it is to the given figure"""
global letter_index
from matplotlib.transforms import Affine2D, ScaledTranslation
@@ -544,25 +655,39 @@ def add_letter(ax: Axes = None, offset: float = 0, offset2: float = 0, letter: s
letter_index += 1
# add a transform that gives the coordinates relative to the left top corner of the axes in cm
- transform = Affine2D().scale(1 / 2.54, 1 / 2.54) + fig.dpi_scale_trans + ScaledTranslation(0, 1, ax.transAxes)
+ transform = (
+ Affine2D().scale(1 / 2.54, 1 / 2.54)
+ + fig.dpi_scale_trans
+ + ScaledTranslation(0, 1, fig.dpi_scale_trans)
+ )
# add a text a the given position
- ax.text(-0.5+offset, offset2, letter, fontproperties=font, transform=transform, ha="center", va="bottom", picker=True)
+ ax.text(
+ -0.5 + offset,
+ offset2,
+ letter,
+ fontproperties=font,
+ transform=transform,
+ ha="center",
+ va="bottom",
+ picker=True,
+ )
def get_letter_font_prop():
- """ get the properties of the subplot letters to add """
+ """get the properties of the subplot letters to add"""
from matplotlib.font_manager import FontProperties
+
font = FontProperties()
font.set_family("C:\\WINDOWS\\Fonts\\HelveticaNeue-CondensedBold.ttf")
font.set_weight("heavy")
font.set_size(10)
- font.letter_format = "a"
+ font.letter_format = "a" # ty:ignore[unresolved-attribute]
return font
def add_letters(*args, **kwargs):
- """ add a letter indicating which subplot it is to all of the axes of the given figure """
+ """add a letter indicating which subplot it is to all of the axes of the given figure"""
for ax in plt.gcf().axes:
add_letter(ax, *args, **kwargs)
@@ -588,9 +713,9 @@ def axes_to_grid(axes=None, track_changes=False):
new_indices = [0, 0]
for i in [0, 1]:
d = np.abs(pos[i] - center[i])
- if len(d) == 0 or np.min(d) > dims[i]/2:
+ if len(d) == 0 or np.min(d) > dims[i] / 2:
pos[i].append(center[i])
- new_indices[i] = len(pos[i])-1
+ new_indices[i] = len(pos[i]) - 1
else:
new_indices[i] = np.argmin(d)
axes_indices.append(new_indices)
@@ -614,21 +739,33 @@ def axes_to_grid(axes=None, track_changes=False):
if x_count == 0:
x_gap = 0
else:
- x_gap = ((x_max-x_min)-(x_count+1)*width)/x_count
+ x_gap = ((x_max - x_min) - (x_count + 1) * width) / x_count
if y_count == 0:
y_gap = 0
else:
- y_gap = ((y_max-y_min)-(y_count+1)*height)/y_count
+ y_gap = ((y_max - y_min) - (y_count + 1) * height) / y_count
# make all the plots the same size and align them on the grid
for i, ax in enumerate(axes):
- ax.set_position([x_min+axes_indices[i][0] * (width+x_gap),
- y_min+axes_indices[i][1] * (height + y_gap),
- width,
- height,
- ])
+ ax.set_position(
+ [
+ x_min + axes_indices[i][0] * (width + x_gap),
+ y_min + axes_indices[i][1] * (height + y_gap),
+ width,
+ height,
+ ]
+ )
if track_changes is True:
- ax.figure.change_tracker.addChange(ax, ".set_position([%f, %f, %f, %f])" % (x_min+axes_indices[i][0] * (width+x_gap), y_min+axes_indices[i][1] * (height + y_gap), width, height))
+ ax.figure.change_tracker.addChange(
+ ax,
+ ".set_position([%f, %f, %f, %f])"
+ % (
+ x_min + axes_indices[i][0] * (width + x_gap),
+ y_min + axes_indices[i][1] * (height + y_gap),
+ width,
+ height,
+ ),
+ )
# make all the plots have the same limits
xmin = np.min([ax.get_xlim()[0] for ax in axes])
@@ -645,17 +782,23 @@ def axes_to_grid(axes=None, track_changes=False):
ax.set_ylabel("")
ax.set_yticklabels([])
if track_changes is True:
- ax.figure.change_tracker.addChange(ax, ".get_yaxis().get_label().set_text('')")
+ ax.figure.change_tracker.addChange(
+ ax, ".get_yaxis().get_label().set_text('')"
+ )
ax.figure.change_tracker.addChange(ax, ".set_yticklabels([])")
if axes_indices[i][1] != 0:
ax.set_xlabel("")
ax.set_xticklabels([])
if track_changes is True:
- ax.figure.change_tracker.addChange(ax, ".get_xaxis().get_label().set_text('')")
+ ax.figure.change_tracker.addChange(
+ ax, ".get_xaxis().get_label().set_text('')"
+ )
ax.figure.change_tracker.addChange(ax, ".set_xticklabels([])")
despine(ax)
if track_changes is True:
- ax.figure.change_tracker.addChange(ax, ".spines['right'].set_visible(False)")
+ ax.figure.change_tracker.addChange(
+ ax, ".spines['right'].set_visible(False)"
+ )
ax.figure.change_tracker.addChange(ax, ".spines['top'].set_visible(False)")
@@ -665,4 +808,4 @@ def main_figure(artist):
if artist.figure == artist:
return artist
else:
- return main_figure(artist.figure)
\ No newline at end of file
+ return main_figure(artist.figure)
diff --git a/pylustrator/jupyter_cells.py b/pylustrator/jupyter_cells.py
index c9010c7..22d0614 100644
--- a/pylustrator/jupyter_cells.py
+++ b/pylustrator/jupyter_cells.py
@@ -24,11 +24,14 @@
the file is instead of a normal file a jupyter notebook and redirects writes accordingly.
"""
+
def setJupyterCellText(text: str):
- """ the function replaces the text in the current jupyter cell with the given text """
- from IPython.display import Javascript, display
+ """the function replaces the text in the current jupyter cell with the given text"""
+ from IPython.display import Javascript, display # ty:ignore[unresolved-import]
+
text = text.replace("\n", "\\n").replace("'", "\\'")
- js = """
+ js = (
+ """
var output_area = this;
// find my cell element
var cell_element = output_area.element.parents('.cell');
@@ -37,35 +40,50 @@ def setJupyterCellText(text: str):
// get the cell object
var cell = Jupyter.notebook.get_cell(cell_idx);
cell.get_text();
- cell.set_text('"""+text+"""');
- console.log('"""+text+"""');
+ cell.set_text('"""
+ + text
+ + """');
+ console.log('"""
+ + text
+ + """');
"""
+ )
display(Javascript(js))
def getIpythonCurrentCell() -> str:
- """ this function returns the text of the current jupyter cell """
+ """this function returns the text of the current jupyter cell"""
import inspect
+
# get the first stack which has a filename starting with "") or ("ipykernel" in filename and not filename.endswith(".tmp")):
+ if (self.filename[0] == "<" and self.filename[-1] == ">") or (
+ "ipykernel" in filename and not filename.endswith(".tmp")
+ ):
self.is_cell = True
self.text = getIpythonCurrentCell()
else:
@@ -75,13 +93,15 @@ def __init__(self, filename, mode, **kwargs):
def __iter__(self):
text = self.text
+ if text is None:
+ return
while len(text):
pos = text.find("\n")
if pos == -1:
yield text
break
- yield text[:pos+1]
- text = text[pos+1:]
+ yield text[: pos + 1]
+ text = text[pos + 1 :]
def write(self, line):
if self.write_text is None:
@@ -93,7 +113,11 @@ def __enter__(self):
def __exit__(self, exc_type, exc_val, exc_tb):
if self.write_text is not None:
- if self.filename[0] == "<" and self.filename[-1] == ">" or ("ipykernel" in filename and not filename.endswith(".tmp")):
+ if (
+ self.filename[0] == "<"
+ and self.filename[-1] == ">"
+ or ("ipykernel" in filename and not filename.endswith(".tmp"))
+ ):
setJupyterCellText(self.write_text)
else:
global_files[self.filename] = self.write_text
diff --git a/pylustrator/lab_colormap.py b/pylustrator/lab_colormap.py
index 3adfbfb..8b2c79b 100644
--- a/pylustrator/lab_colormap.py
+++ b/pylustrator/lab_colormap.py
@@ -1,129 +1,143 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# lab_colormap.py
-
-# Copyright (c) 2016-2020, Richard Gerum
-#
-# This file is part of Pylustrator.
-#
-# Pylustrator is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pylustrator is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pylustrator. If not, see
-
-""" Colormap """
-import numpy as np
-from typing import Sequence, Union
-from matplotlib.colors import Colormap, ListedColormap, to_rgb
-
-
-class CmapColor(list):
- """ a class that appends metadate to a color """
- def setMeta(self, value, cmap):
- self.value = value
- self.cmap = cmap
-
-
-def convert_rgb2lab(colors: Sequence):
- """ convert colors from rgb to lab color space """
- from skimage.color import rgb2lab
- return [rgb2lab(np.array(c)[None, None, :3]) for c in colors]
-
-
-def convert_lab2rgb(colors):
- """ convert colors from lab to rgb color space """
- from skimage.color import lab2rgb
- return [lab2rgb(np.array(c))[0, 0, :3] for c in colors]
-
-
-class LabColormap(ListedColormap):
- """ a custom colormap that blends between N custom colors """
- init_colors = None
-
- def __init__(self, colors: Sequence, N: int, stops=None):
- """ initialize with the given colors and stops """
- # store stops
- self.stops = stops
- # set colors
- self.set_color(colors)
- # initialize
- Colormap.__init__(self, "test", N)
-
- def _init(self):
- """ generate the colormap from the given colors (used by ListedColormap) """
- # convert to lab
- lab_colors = convert_rgb2lab(self.init_colors)
- # initialize new list
- colors = []
- # iterate over stops
- stops = self.get_stops()
- for j in range(len(self.init_colors) - 1):
- # interpolate between stops in lab
- for i in np.linspace(stops[j], stops[j + 1], int(self.N / (len(stops) - 1))):
- colors.append(lab_colors[j] * (1 - i) + i * lab_colors[j + 1])
- # convert back to rgb
- self.colors = convert_lab2rgb(colors)
- # initialize a listed colormap
- ListedColormap._init(self)
-
- def __call__(self, value: float, *args, **kwargs):
- """ get the color associated with the given value from the colormap """
- # get the color
- result = Colormap.__call__(self, value, *args, **kwargs)
- # add meta values to it
- result = CmapColor(result)
- result.setMeta(value, self)
- # return the color
- return result
-
- def get_color(self) -> Sequence:
- """ return all the colors """
- # return the colors
- return self.init_colors
-
- def set_color(self, color: Union[str, Sequence], index: int = None):
- """ set a color to the given index """
- # update the color according to the index
- if index is not None:
- self.init_colors[index] = to_rgb(color)
- # or update the whole list
- else:
- # ensure that the colors are rgb
- self.init_colors = [to_rgb(c) for c in color]
- # linearize the lightness
- self.linearize_lightness()
- # notify that we have to reinitialize the colormap
- self._isinit = False
-
- def get_stops(self) -> Sequence:
- """ return the stops """
- # get the stops
- stops = self.stops
- # if they are not defined, interpolate from 0 to 1
- if stops is None:
- stops = np.linspace(0, 1, len(self.init_colors))
- # return the stops
- return stops
-
- def linearize_lightness(self):
- """ linearize the lightness of the colors in the colormap """
- # convert to lab
- lab_colors = convert_rgb2lab(self.init_colors)
- # define start and end lightness
- lightness_start = lab_colors[0][0, 0, 0]
- lichtness_end = lab_colors[-1][0, 0, 0]
- # iterate over stops
- stops = self.get_stops()
- for j in range(1, len(stops) - 1):
- # interpolate lightness value
- lab_colors[j][0, 0, 0] = lightness_start * (1 - stops[j]) + stops[j] * lichtness_end
- # convert back to rgb
- self.init_colors = convert_lab2rgb(lab_colors)
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# lab_colormap.py
+
+# Copyright (c) 2016-2020, Richard Gerum
+#
+# This file is part of Pylustrator.
+#
+# Pylustrator is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pylustrator is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pylustrator. If not, see
+
+"""Colormap"""
+
+import numpy as np
+from typing import Sequence, Union, List, Optional
+from matplotlib.colors import Colormap, ListedColormap, to_rgb
+
+
+class CmapColor(list):
+ """a class that appends metadate to a color"""
+
+ def setMeta(self, value, cmap):
+ self.value = value
+ self.cmap = cmap
+
+
+def convert_rgb2lab(colors: Sequence):
+ """convert colors from rgb to lab color space"""
+ from skimage.color import rgb2lab
+
+ return [rgb2lab(np.array(c)[None, None, :3]) for c in colors]
+
+
+def convert_lab2rgb(colors):
+ """convert colors from lab to rgb color space"""
+ from skimage.color import lab2rgb
+
+ return [lab2rgb(np.array(c))[0, 0, :3] for c in colors]
+
+
+class LabColormap(ListedColormap):
+ """a custom colormap that blends between N custom colors"""
+
+ init_colors: Optional[List]
+
+ def __init__(self, colors: Sequence, N: int, stops=None):
+ """initialize with the given colors and stops"""
+ # store stops
+ self.stops = stops
+ # set colors
+ self.set_color(colors)
+ # initialize
+ Colormap.__init__(self, "test", N)
+
+ def _init(self):
+ """generate the colormap from the given colors (used by ListedColormap)"""
+ assert self.init_colors is not None, (
+ "init_colors must be set before calling _init"
+ )
+ # convert to lab
+ lab_colors = convert_rgb2lab(self.init_colors)
+ # initialize new list
+ colors = []
+ # iterate over stops
+ stops = self.get_stops()
+ for j in range(len(self.init_colors) - 1):
+ # interpolate between stops in lab
+ for i in np.linspace(
+ stops[j], stops[j + 1], int(self.N / (len(stops) - 1))
+ ):
+ colors.append(lab_colors[j] * (1 - i) + i * lab_colors[j + 1])
+ # convert back to rgb
+ self.colors = convert_lab2rgb(colors)
+ # initialize a listed colormap
+ ListedColormap._init(self) # ty:ignore[unresolved-attribute]
+
+ def __call__(self, value: float, *args, **kwargs): # ty:ignore[invalid-method-override]
+ """get the color associated with the given value from the colormap"""
+ # get the color
+ result = Colormap.__call__(self, value, *args, **kwargs)
+ # add meta values to it
+ result = CmapColor(result)
+ result.setMeta(value, self)
+ # return the color
+ return result
+
+ def get_color(self) -> list | None:
+ """return all the colors"""
+ # return the colors
+ return self.init_colors
+
+ def set_color(self, color: Union[str, Sequence], index: Optional[int] = None):
+ """set a color to the given index"""
+ # update the color according to the index
+ if index is not None and self.init_colors is not None:
+ self.init_colors[index] = to_rgb(color)
+ # or update the whole list
+ else:
+ # ensure that the colors are rgb
+ self.init_colors = [to_rgb(c) for c in color]
+ # linearize the lightness
+ self.linearize_lightness()
+ # notify that we have to reinitialize the colormap
+ self._isinit = False
+
+ def get_stops(self) -> Sequence:
+ """return the stops"""
+ # get the stops
+ stops = self.stops
+ # if they are not defined, interpolate from 0 to 1
+ if stops is None:
+ assert self.init_colors is not None, "init_colors must be set"
+ stops = np.linspace(0, 1, len(self.init_colors))
+ # return the stops
+ return stops
+
+ def linearize_lightness(self):
+ """linearize the lightness of the colors in the colormap"""
+ assert self.init_colors is not None, "init_colors must be set"
+ # convert to lab
+ lab_colors = convert_rgb2lab(self.init_colors)
+ # define start and end lightness
+ lightness_start = lab_colors[0][0, 0, 0]
+ lichtness_end = lab_colors[-1][0, 0, 0]
+ # iterate over stops
+ stops = self.get_stops()
+ for j in range(1, len(stops) - 1):
+ # interpolate lightness value
+ lab_colors[j][0, 0, 0] = (
+ lightness_start * (1 - stops[j]) + stops[j] * lichtness_end
+ )
+ # convert back to rgb
+ self.init_colors = convert_lab2rgb(lab_colors)
diff --git a/pylustrator/parse_svg.py b/pylustrator/parse_svg.py
index 020c0fd..f93a910 100644
--- a/pylustrator/parse_svg.py
+++ b/pylustrator/parse_svg.py
@@ -1,732 +1,958 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# parse_svg.py
-
-# Copyright (c) 2016-2020, Richard Gerum
-#
-# This file is part of Pylustrator.
-#
-# Pylustrator is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pylustrator is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pylustrator. If not, see
-
-from xml.dom import minidom
-import matplotlib.colors as mcolors
-import matplotlib.pyplot as plt
-import matplotlib.patches as mpatches
-import matplotlib.transforms as mtransforms
-import matplotlib.path as mpath
-from matplotlib.textpath import TextPath
-from matplotlib.font_manager import FontProperties
-import sys
-import numpy as np
-import re
-import io
-import base64
-import matplotlib.text
-from .arc2bez import arcToBezier
-
-
-def deform(base_trans: mtransforms.Transform, x: float, y: float, sx: float = 0, sy: float = 0):
- """ apply an affine transformation to the given transformation """
- return mtransforms.Affine2D([[x, sx, 0], [sy, y, 0], [0, 0, 1]]) + base_trans
-
-
-def parseTransformation(transform_text: str) -> mtransforms.Transform:
- """ convert a transform string in the svg file to a matplotlib transformation """
- base_trans = mtransforms.IdentityTransform()
- if transform_text is None or transform_text == "":
- return base_trans
- transformations_list = re.findall(r"\w*\([-.,\d\s]*\)", transform_text)
- for transform_text in transformations_list:
- data = [float(s) for s in re.findall(r"[-.\d]+", transform_text)]
- command = re.findall(r"^\w+", transform_text)[0]
- if command == "translate":
- try:
- ox, oy = data
- except ValueError:
- ox, oy = data[0], data[0]
- base_trans = mtransforms.Affine2D([[1, 0, ox], [0, 1, oy], [0, 0, 1]]) + base_trans
- elif command == "rotate":
- a = np.deg2rad(data[0])
- ca, sa = np.cos(a), np.sin(a)
- base_trans = mtransforms.Affine2D([[ca, -sa, 0], [sa, ca, 0], [0, 0, 1]]) + base_trans
- elif command == "scale":
- if len(data) >= 2:
- x, y = data
- else:
- x, y = data[0], data[0]
- base_trans = mtransforms.Affine2D([[x, 0, 0], [0, y, 0], [0, 0, 1]]) + base_trans
- elif command == "skewX":
- x, = data
- x = np.tan(x*np.pi/180)
- base_trans = mtransforms.Affine2D([[1, x, 0], [0, 1, 0], [0, 0, 1]]) + base_trans
- elif command == "skewY":
- y, = data
- y = np.tan(y*np.pi/180)
- base_trans = mtransforms.Affine2D([[1, 0, 0], [y, 1, 0], [0, 0, 1]]) + base_trans
- elif command == "matrix":
- x, sy, sx, y, ox, oy = data
- base_trans = mtransforms.Affine2D([[x, sx, ox], [sy, y, oy], [0, 0, 1]]) + base_trans
- else:
- print("ERROR: unknown transformation", transform_text)
- return base_trans
-
-
-def get_inline_style(node: minidom.Element, base_style: dict = None) -> dict:
- """ update the basestyle with the style defined by the style property of the node """
- style = {}
- if base_style is not None:
- style.update(base_style)
- attribute_names = ["alignment-baseline", "baseline-shift", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-rendering", "cursor", "direction", "display", "dominant-baseline", "fill", "fill-opacity", "fill-rule", "filter", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "glyph-orientation-horizontal", "glyph-orientation-vertical", "image-rendering", "letter-spacing", "lighting-color", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "overflow", "paint-order", "pointer-events", "shape-rendering", "stop-color", "stop-opacity", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-anchor", "text-decoration", "text-overflow", "text-rendering", "unicode-bidi", "vector-effect", "visibility", "white-space", "word-spacing", "writing-mode"]
- for name in attribute_names:
- value = node.getAttribute(name)
- if value != "":
- style[name] = value
-
- for element in node.getAttribute("style").split(";"):
- if element == "":
- continue
- key, value = element.split(":", 1)
- style[key] = value
- return style
-
-
-def get_css_style(node: minidom.Element, css_list: list, base_style: dict) -> dict:
- """ update the base_style with the style definitions from the stylesheet that are applicable to the node
- defined by the classes or id of the node
- """
- style = {}
- if base_style is not None:
- style.update(base_style)
- classes = node.getAttribute("class").split()
- for css in css_list:
- css_condition, css_style = css
- if css_condition[0] == "." and css_condition[1:] in classes:
- style.update(css_style)
- elif css_condition[0] == "#" and css_condition[1:] == node.getAttribute("id"):
- style.update(css_style)
- elif css_condition == node.tagName:
- style.update(css_style)
- return style
-
-
-def apply_style(style: dict, patch: mpatches.Patch) -> dict:
- """ apply the properties defined in style to the given patch """
- fill_opacity = float(style.get("opacity", 1)) * float(style.get("fill-opacity", 1))
- stroke_opacity = float(style.get("opacity", 1)) * float(style.get("stroke-opacity", 1))
-
- def readColor(value):
- try:
- return mcolors.to_rgb(value)
- except:
- # matplotlib cannot handle html colors in the form #000
- if len(value) == 4 and value[0] == "#":
- return readColor("#"+value[1]*2+value[2]*2+value[3]*2)
- raise
-
- # matplotlib defaults differ
- if "fill" not in style:
- style["fill"] = "none"
- if "stroke" not in style:
- style["stroke"] = "none"
-
- for key, value in style.items():
- try:
- if key == "opacity":
- pass
- #patch.set_alpha(float(value))
- elif key == "fill":
- if value == "none" or value == "transparent":
- patch.set_facecolor("none")
- else:
- try:
- r, g, b = readColor(value)
- patch.set_facecolor((r, g, b, fill_opacity))
- except Exception as err:
- patch.set_facecolor("none")
- raise
- elif key == "fill-opacity":
- pass
- elif key == "stroke":
- if value == "none" or value == "transparent":
- patch.set_edgecolor("none")
- else:
- try:
- r, g, b = readColor(value)
- patch.set_edgecolor((r, g, b, stroke_opacity))
- except Exception as err:
- patch.set_edgecolor("none")
- raise
- elif key == "stroke-opacity":
- pass
- elif key == "stroke-dasharray":
- if value != "none":
- offset = 0
- if isinstance(patch.get_linestyle(), tuple):
- offset, dashes = patch.get_linestyle()
- patch.set_linestyle((offset, [float(s)*4 for s in value.split(",")]))
- elif key == "stroke-dashoffset":
- dashes = [1, 0]
- if isinstance(patch.get_linestyle(), tuple):
- offset, dashes = patch.get_linestyle()
- patch.set_linestyle((float(value)*4, dashes))
- elif key == "stroke-linecap":
- if value == "square":
- value = "projecting"
- patch.set_capstyle(value)
- elif key == "stroke-linejoin":
- patch.set_joinstyle(value)
- elif key == "stroke-miterlimit":
- pass # unfortunately we cannot implement this in matplotlib
- elif key == "stroke-width":
- try:
- patch.set_linewidth(svgUnitToMpl(value))
- except:
- pass
- elif key == "stroke-linecap":
- try:
- patch.set_dash_capstyle(value)
- patch.set_solid_capstyle(value)
- except AttributeError:
- pass
- elif key == "font-size":
- pass
- elif key == "font-weight":
- pass
- elif key == "font-style":
- pass
- elif key == "font-family":
- pass
- elif key == "font-variant":
- pass
- elif key == "font-stretch":
- pass
- elif key == "display":
- pass
- elif key == "text-anchor":
- pass
- else:
- print("ERROR: unknown style key", key, file=sys.stderr)
- except ValueError:
- print("ERROR: could not set style", key, value, file=sys.stderr)
- return style
-
-
-def font_properties_from_style(style: dict) -> FontProperties:
- """ convert a style to a FontProperties object """
- fp = FontProperties()
- for key, value in style.items():
- if key == "font-family":
- fp.set_family(value)
- if key == "font-size":
- fp.set_size(svgUnitToMpl(value))
- if key == "font-weight":
- fp.set_weight(value)
- if key == "font-style":
- fp.set_style(value)
- if key == "font-variant":
- fp.set_variant(value)
- if key == "font-stretch":
- fp.set_stretch(value)
- return fp
-
-
-def styleNoDisplay(style: dict) -> bool:
- """ check whether the style defines not to display the element """
- return style.get("display", "inline") == "none" or \
- style.get("visibility", "visible") == "hidden" or \
- style.get("visibility", "visible") == "collapse"
-
-
-def plt_patch(node: minidom.Element, trans_parent_trans: mtransforms.Transform, style: dict, constructor: callable, ids: dict, no_draw: bool = False) -> mpatches.Patch:
- """ add a node to the figure by calling the provided constructor """
- trans_node = parseTransformation(node.getAttribute("transform"))
- style = get_inline_style(node, get_css_style(node, ids["css"], style))
-
- patch = constructor(node, trans_node + trans_parent_trans + plt.gca().transData, style, ids)
- if not isinstance(patch, list):
- patch = [patch]
-
- for p in patch:
- if not getattr(p, "is_marker", False):
- style = apply_style(style, p)
- p.style = style
- #p.set_transform(p.get_transform() + plt.gca().transData)
- p.trans_parent = trans_parent_trans
- p.trans_node = parseTransformation(node.getAttribute("transform"))
-
- if not no_draw and not styleNoDisplay(style):
- plt.gca().add_patch(p)
- if node.getAttribute("id") != "":
- ids[node.getAttribute("id")] = patch
- return patch
-
-
-def clone_patch(patch: mpatches.Patch) -> mpatches.Patch:
- """ clone a patch element with the same properties as the given patch """
- if isinstance(patch, mpatches.Rectangle):
- return mpatches.Rectangle(xy=patch.get_xy(),
- width=patch.get_width(),
- height=patch.get_height())
- if isinstance(patch, mpatches.Circle):
- return mpatches.Circle(xy=patch.get_xy(),
- radius=patch.get_radius())
- if isinstance(patch, mpatches.Ellipse):
- return mpatches.Ellipse(xy=patch.get_xy(),
- width=patch.get_width(),
- height=patch.get_height())
- if isinstance(patch, mpatches.PathPatch):
- return mpatches.PathPatch(patch.get_path())
-
-
-def patch_rect(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> mpatches.Rectangle:
- """ draw a svg rectangle node as a rectangle patch element into the figure (with the given transformation and style) """
- if node.getAttribute("d") != "":
- return patch_path(node, trans, style, ids)
- if node.getAttribute("ry") != "" and node.getAttribute("ry") != 0:
- return mpatches.FancyBboxPatch(xy=(float(node.getAttribute("x")), float(node.getAttribute("y"))),
- width=float(node.getAttribute("width")),
- height=float(node.getAttribute("height")),
- boxstyle=mpatches.BoxStyle.Round(0, float(node.getAttribute("ry"))),
- transform=trans)
- return mpatches.Rectangle(xy=(float(node.getAttribute("x")), float(node.getAttribute("y"))),
- width=float(node.getAttribute("width")),
- height=float(node.getAttribute("height")),
- transform=trans)
-
-
-def patch_ellipse(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> mpatches.Ellipse:
- """ draw a svg ellipse node as a ellipse patch element into the figure (with the given transformation and style) """
- if node.getAttribute("d") != "":
- return patch_path(node, trans, style, ids)
- return mpatches.Ellipse(xy=(float(node.getAttribute("cx")), float(node.getAttribute("cy"))),
- width=float(node.getAttribute("rx"))*2,
- height=float(node.getAttribute("ry"))*2,
- transform=trans)
-
-
-def patch_circle(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> mpatches.Circle:
- """ draw a svg circle node as a circle patch element into the figure (with the given transformation and style) """
- if node.getAttribute("d") != "":
- return patch_path(node, trans, style, ids)
- return mpatches.Circle(xy=(float(node.getAttribute("cx")), float(node.getAttribute("cy"))),
- radius=float(node.getAttribute("r")),
- transform=trans)
-
-
-def plt_draw_text(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict, no_draw: bool = False):
- """ draw a svg text node as a text patch element into the figure (with the given transformation and style) """
- trans = parseTransformation(node.getAttribute("transform")) + trans + plt.gca().transData
- trans = mtransforms.Affine2D([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) + trans
- if node.getAttribute("x") != "":
- pos = np.array([svgUnitToMpl(node.getAttribute("x")), -svgUnitToMpl(node.getAttribute("y"))])
- else:
- pos = np.array([0, 0])
-
- style = get_inline_style(node, get_css_style(node, ids["css"], style))
-
- dx = node.getAttribute("dx") or "0"
- dy = node.getAttribute("dy") or "0"
-
- text_content = ""
- patch_list = []
- for child in node.childNodes:
- if not isinstance(child, minidom.Element):
- partial_content = child.data
- pos_child = pos.copy()
-
- pos_child[0] += svgUnitToMpl(dx)
- pos_child[1] -= svgUnitToMpl(dy)
- style_child = style
- part_id = ""
- else:
- part_id = node.getAttribute("id")
- if child.firstChild is None:
- continue
- partial_content = child.firstChild.nodeValue
- style_child = get_inline_style(child, get_css_style(child, ids["css"], style))
- pos_child = pos.copy()
- if child.getAttribute("x") != "":
- pos_child = np.array([svgUnitToMpl(child.getAttribute("x")), -svgUnitToMpl(child.getAttribute("y"))])
- if child.getAttribute("dx") != "":
- pos_child[0] += svgUnitToMpl(child.getAttribute("dx"))
- if child.getAttribute("dy") != "":
- pos_child[1] -= svgUnitToMpl(child.getAttribute("dy"))
-
- text_content += partial_content
- path1 = TextPath(pos_child,
- partial_content,
- prop=font_properties_from_style(style_child))
- patch = mpatches.PathPatch(path1, transform=trans)
-
- apply_style(style_child, patch)
- if not no_draw and not styleNoDisplay(style_child):
- plt.gca().add_patch(patch)
- if part_id != "":
- ids[part_id] = patch
- patch_list.append(patch)
-
- if node.getAttribute("id") != "":
- ids[node.getAttribute("id")] = patch_list
-
-
-def patch_path(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> list:
- """ draw a path svg node by using a matplotlib path patch (with the given transform and style) """
-
-
- start_pos = None
- command = None
- verts = []
- codes = []
- angles = []
-
- current_pos = np.array([0, 0])
-
- elements = [a[0] for a in re.findall(r'(([-+]?\d*\.?\d+(?:e[-+]?\d*\.?\d+)?)|\w)', node.getAttribute("d"))]
- elements.reverse()
-
- def popPos():
- pos = np.array([float(elements.pop()), float(elements.pop())])
- if not absolute:
- pos += current_pos
- return pos
-
- def popValue(count=None):
- if count is None:
- return float(elements.pop())
- else:
- return [float(elements.pop()) for i in range(count)]
-
- def addPathElement(type, *positions, no_angle=False):
- for pos in positions:
- verts.append(pos)
- codes.append(type)
-
- def vec2angle(vec):
- return np.arctan2(vec[1], vec[0])
-
- if not no_angle:
- n = len(positions)
- angles[-1].append(vec2angle(verts[-n] - verts[-n-1]))
- for i in range(n-1):
- angles.append([])
- angles.append([vec2angle(verts[-1] - verts[-2])])
- else:
- angles.append([])
- return positions[-1]
-
- i = len(elements)
- while elements:
- # if things go wrong for some reason prevent endless loops
- i -= 1
- if i <= 0:
- break
- if 'A' <= elements[-1] <= 'z':
- last_command = command
- command = elements.pop()
- absolute = command.isupper()
- command = command.lower()
-
- # moveto
- if command == "m":
- current_pos = addPathElement(mpath.Path.MOVETO, popPos(), no_angle=True)
- start_pos = current_pos
-
- command = "l"
- # close
- elif command == "z":
- # Close path
- current_pos = addPathElement(mpath.Path.CLOSEPOLY, start_pos, no_angle=True)
-
- start_pos = None
- command = None # You can't have implicit commands after closing.
- # lineto
- elif command == 'l':
- current_pos = addPathElement(mpath.Path.LINETO, popPos())
-
- # horizontal lineto
- elif command == 'h':
- current_pos = addPathElement(mpath.Path.LINETO,
- np.array([popValue()+current_pos[0]*(1-absolute), current_pos[1]]))
- # vertical lineto
- elif command == 'v':
- current_pos = addPathElement(mpath.Path.LINETO,
- np.array([current_pos[0], popValue() + current_pos[1] * (1 - absolute)]))
- # cubic bezier curveto
- elif command == 'c':
- current_pos = addPathElement(mpath.Path.CURVE4, popPos(), popPos(), popPos())
-
- # smooth cubic bezier curveto
- elif command == 's':
- # Smooth curve. First control point is the "reflection" of
- # the second control point in the previous path.
-
- if last_command not in 'cs':
- # If there is no previous command or if the previous command
- # was not an C, c, S or s, assume the first control point is
- # coincident with the current point.
- control1 = current_pos
- else:
- # The first control point is assumed to be the reflection of
- # the second control point on the previous command relative
- # to the current point.
- control1 = current_pos + (current_pos - verts[-2])
-
- current_pos = addPathElement(mpath.Path.CURVE4, control1, popPos(), popPos())
-
- # quadratic bezier curveto
- elif command == 'q':
- current_pos = addPathElement(mpath.Path.CURVE3, popPos(), popPos())
-
- # smooth quadratic bezier curveto
- elif command == 't':
- # Smooth curve. Control point is the "reflection" of
- # the second control point in the previous path.
-
- if last_command not in 'qt':
- # If there is no previous command or if the previous command
- # was not an Q, q, T or t, assume the first control point is
- # coincident with the current point.
- control1 = current_pos
- else:
- # The control point is assumed to be the reflection of
- # the control point on the previous command relative
- # to the current point.
- control1 = current_pos + (current_pos - verts[-2])
- current_pos = addPathElement(mpath.Path.CURVE3, control1, popPos())
-
- # elliptical arc
- elif command == 'a':
- radius1, radius2, rotation, arc, sweep = popValue(5)
- end = popPos()
-
- current_pos = addPathElement(mpath.Path.CURVE4, *arcToBezier(current_pos, end, radius1, radius2, rotation, arc, sweep))
-
- # average angles when a point has more than one line
- for i in range(len(angles)):
- if len(angles[i]) == 1:
- angles[i] = angles[i][0]
- else:
- angles[i] = np.arctan2(np.mean(np.sin(angles[i])), np.mean(np.cos(angles[i])))
-
- def addMarker(i, name):
- marker_style, patches = ids[name]
- def add_list_elements(element):
- if isinstance(element, list):
- for e in element:
- add_list_elements(e)
- else:
- parent_patch = element
- patch = clone_patch(parent_patch)
- apply_style(parent_patch.style, patch)
-
- a = angles[i]
- ca, sa = np.cos(a), np.sin(a)
- ox, oy = verts[i]
- trans2 = parent_patch.trans_node + mtransforms.Affine2D([[ca, -sa, ox], [sa, ca, oy], [0, 0, 1]]) + parent_patch.trans_parent + trans#+ plt.gca().transAxes
- if marker_style.get("markerUnits", "strokeWidth") == "strokeWidth":
- s = svgUnitToMpl(style["stroke-width"])
- trans2 = mtransforms.Affine2D([[s, 0, 0], [0, s, 0], [0, 0, 1]]) + trans2
- patch.set_transform(trans2)
- patch.is_marker = True
- patch_list.append(patch)
- add_list_elements(patches)
-
- patch_list = []
-
- if len(verts) == 0:
- return patch_list
- path = mpath.Path(verts, codes)
- patch_list.append(mpatches.PathPatch(path, transform=trans))
-
- if style.get("marker-start"):
- if style.get("marker-start").startswith("url(#"):
- name = style.get("marker-start")[len("url(#"):-1]
- if name in ids:
- addMarker(0, name)
- if style.get("marker-mid"):
- if style.get("marker-mid").startswith("url(#"):
- name = style.get("marker-mid")[len("url(#"):-1]
- if name in ids:
- for i in range(1, len(angles)-1):
- addMarker(i, name)
- if style.get("marker-end"):
- if style.get("marker-end").startswith("url(#"):
- name = style.get("marker-end")[len("url(#"):-1]
- if name in ids:
- addMarker(len(angles)-1, name)
-
- return patch_list
-
-
-def svgUnitToMpl(unit: str, default=None) -> float:
- """ convert a unit text to svg pixels """
- import re
- if unit == "":
- return default
- match = re.match(r"^([-.\d]*)(\w*).*$", unit)
- if match:
- value, unit = match.groups()
- value = float(value)
- if unit == "pt":
- value *= plt.gcf().dpi / 72
- elif unit == "pc":
- value *= plt.gcf().dpi / 6
- elif unit == "in":
- value *= plt.gcf().dpi
- elif unit == "px":
- pass
- elif unit == "cm":
- value *= plt.gcf().dpi / 2.5
- elif unit == "mm":
- value *= plt.gcf().dpi / 25
- return value
-
-
-def openImageFromLink(link: str) -> np.ndarray:
- """ load an embedded image file or an externally liked image file"""
- if link.startswith("file:///"):
- return plt.imread(link[len("file:///"):])
- else:
- type, data = re.match(r"data:image/(\w*);base64,(.*)", link).groups()
-
- data = base64.decodebytes(bytes(data, "utf-8"))
-
- buf = io.BytesIO()
- buf.write(data)
- buf.seek(0)
- im = plt.imread(buf, format=type)
- buf.close()
- return im
-
-
-def parseStyleSheet(text: str) -> list:
- """ parse a style sheet text """
- # remove line comments
- text = re.sub("//.*?\n", "", text)
- # remove multiline comments
- text = text.replace("\n", " ")
- text = re.sub(r"/\*.*?\*/", "", text)
- text = re.sub(r"/\*.*?\*/", "", text)
-
- style_definitions = []
- styles = re.findall("[^}]*{[^}]*}", text)
- for style in styles:
- condition, main = style.split("{", 1)
- parts = [part.strip().split(":", 1) for part in main[:-1].split(";") if part.strip() != ""]
- style_dict = {k: v.strip() for k, v in parts}
- for cond in condition.split(","):
- style_definitions.append([cond.strip(), style_dict])
- return style_definitions
-
-
-def parseGroup(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict, no_draw: bool = False) -> list:
- """ parse the children of a group node with the inherited transformation and style """
- trans = parseTransformation(node.getAttribute("transform")) + trans
- style = get_inline_style(node, style)
-
- patch_list = []
- for child in node.childNodes:
- if child.nodeType == child.TEXT_NODE or child.nodeType == child.COMMENT_NODE:
- continue
- if child.tagName == "style":
- for childchild in child.childNodes:
- if childchild.nodeType == childchild.CDATA_SECTION_NODE:
- ids["css"].extend(parseStyleSheet(childchild.wholeText))
- elif child.tagName == "rect":
- patch_list.append(plt_patch(child, trans, style, patch_rect, ids, no_draw=no_draw))
- elif child.tagName == "ellipse":
- patch_list.append(plt_patch(child, trans, style, patch_ellipse, ids, no_draw=no_draw))
- elif child.tagName == "circle":
- patch_list.append(plt_patch(child, trans, style, patch_circle, ids, no_draw=no_draw))
- elif child.tagName == "path":
- patch_list.append(plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw))
- elif child.tagName == "polygon":
- # matplotlib has a designated polygon patch, but it is easier to just convert it to a path
- child.setAttribute("d", "M " + child.getAttribute("points") + " Z")
- patch_list.append(plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw))
- elif child.tagName == "polyline":
- child.setAttribute("d", "M " + child.getAttribute("points"))
- patch_list.append(plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw))
- elif child.tagName == "line":
- child.setAttribute("d", "M " + child.getAttribute("x1") + "," + child.getAttribute("y1") + " " + child.getAttribute("x2") + "," + child.getAttribute("y2"))
- patch_list.append(plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw))
- elif child.tagName == "g":
- patch_list.append(parseGroup(child, trans, style, ids, no_draw=(no_draw or styleNoDisplay(style))))
- elif child.tagName == "text":
- patch_list.append(plt_draw_text(child, trans, style, ids, no_draw=no_draw))
- elif child.tagName == "defs":
- patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
- elif child.tagName == "clipPath":
- patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
- elif child.tagName == "symbol":
- patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
- elif child.tagName == "marker":
- patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
- elif child.tagName == "sodipodi:namedview":
- pass # used for some inkscape metadata
- elif child.tagName == "image":
- link = child.getAttribute("xlink:href")
- im = openImageFromLink(link)
- if no_draw is False:
- if child.getAttribute("x") != "":
- im_patch = plt.imshow(im[::-1], extent=[svgUnitToMpl(child.getAttribute("x")), svgUnitToMpl(child.getAttribute("x")) + svgUnitToMpl(child.getAttribute("width")),
- svgUnitToMpl(child.getAttribute("y")), svgUnitToMpl(child.getAttribute("y")) + svgUnitToMpl(child.getAttribute("height")),
- ], zorder=1)
- else:
- pass#im_patch = plt.imshow(im[::-1], zorder=1)
- #patch_list.append(im_patch)
- elif child.tagName == "metadata":
- pass # we do not have to draw metadata
- else:
- print("Unknown tag", child.tagName, file=sys.stderr)
-
- if node.getAttribute("id") != "":
- ids[node.getAttribute("id")] = [style, patch_list]
-
- return patch_list
-
-
-def svgread(filename: str):
- """ read an SVG file """
- doc = minidom.parse(filename)
-
- svg = doc.getElementsByTagName("svg")[0]
- try:
- x1, y1, x2, y2 = [svgUnitToMpl(s.strip()) for s in svg.getAttribute("viewBox").split()]
- width, height = (x2 - x1)/plt.gcf().dpi, (y2 - y1)/plt.gcf().dpi
- if max([width, height]) > 8:
- f = 8/max([width, height])
- plt.gcf().set_size_inches(width*f, height*f)
- else:
- plt.gcf().set_size_inches(width, height)
- except ValueError:
- width = svgUnitToMpl(svg.getAttribute("width"), default=100)
- height = svgUnitToMpl(svg.getAttribute("height"), default=100)
- x1, y1, x2, y2 = 0, 0, width, height
- width /= plt.gcf().dpi
- height /= plt.gcf().dpi
- if max([width, height]) > 8:
- f = 8/max([width, height])
- plt.gcf().set_size_inches(width*f, height*f)
- else:
- plt.gcf().set_size_inches(width, height)
- ax = plt.axes([0, 0, 1, 1], label=filename, frameon=False)
- plt.xticks([])
- plt.yticks([])
- for spine in ["left", "right", "top", "bottom"]:
- ax.spines[spine].set_visible(False)
- plt.xlim(x1, x2)
- plt.ylim(y2, y1)
-
- parseGroup(doc.getElementsByTagName("svg")[0], mtransforms.IdentityTransform(), {}, {"css": []})
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# parse_svg.py
+
+# Copyright (c) 2016-2020, Richard Gerum
+#
+# This file is part of Pylustrator.
+#
+# Pylustrator is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pylustrator is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pylustrator. If not, see
+
+from xml.dom import minidom
+from typing import Callable
+import matplotlib.colors as mcolors
+import matplotlib.pyplot as plt
+import matplotlib.patches as mpatches
+import matplotlib.transforms as mtransforms
+import matplotlib.path as mpath
+from matplotlib.textpath import TextPath
+from matplotlib.font_manager import FontProperties
+import sys
+import numpy as np
+import re
+import io
+import base64
+from .arc2bez import arcToBezier
+
+
+def deform(
+ base_trans: mtransforms.Transform, x: float, y: float, sx: float = 0, sy: float = 0
+):
+ """apply an affine transformation to the given transformation"""
+ return mtransforms.Affine2D([[x, sx, 0], [sy, y, 0], [0, 0, 1]]) + base_trans
+
+
+def parseTransformation(transform_text: str) -> mtransforms.Transform:
+ """convert a transform string in the svg file to a matplotlib transformation"""
+ base_trans = mtransforms.IdentityTransform()
+ if transform_text is None or transform_text == "":
+ return base_trans
+ transformations_list = re.findall(r"\w*\([-.,\d\s]*\)", transform_text)
+ for transform_text in transformations_list:
+ data = [float(s) for s in re.findall(r"[-.\d]+", transform_text)]
+ command = re.findall(r"^\w+", transform_text)[0]
+ if command == "translate":
+ try:
+ ox, oy = data
+ except ValueError:
+ ox, oy = data[0], data[0]
+ base_trans = (
+ mtransforms.Affine2D([[1, 0, ox], [0, 1, oy], [0, 0, 1]]) + base_trans
+ )
+ elif command == "rotate":
+ a = np.deg2rad(data[0])
+ ca, sa = np.cos(a), np.sin(a)
+ base_trans = (
+ mtransforms.Affine2D([[ca, -sa, 0], [sa, ca, 0], [0, 0, 1]])
+ + base_trans
+ )
+ elif command == "scale":
+ if len(data) >= 2:
+ x, y = data
+ else:
+ x, y = data[0], data[0]
+ base_trans = (
+ mtransforms.Affine2D([[x, 0, 0], [0, y, 0], [0, 0, 1]]) + base_trans
+ )
+ elif command == "skewX":
+ (x,) = data
+ x = np.tan(x * np.pi / 180)
+ base_trans = (
+ mtransforms.Affine2D([[1, x, 0], [0, 1, 0], [0, 0, 1]]) + base_trans
+ )
+ elif command == "skewY":
+ (y,) = data
+ y = np.tan(y * np.pi / 180)
+ base_trans = (
+ mtransforms.Affine2D([[1, 0, 0], [y, 1, 0], [0, 0, 1]]) + base_trans
+ )
+ elif command == "matrix":
+ x, sy, sx, y, ox, oy = data
+ base_trans = (
+ mtransforms.Affine2D([[x, sx, ox], [sy, y, oy], [0, 0, 1]]) + base_trans
+ )
+ else:
+ print("ERROR: unknown transformation", transform_text)
+ return base_trans
+
+
+def get_inline_style(node: minidom.Element, base_style: dict | None = None) -> dict:
+ """update the basestyle with the style defined by the style property of the node"""
+ style = {}
+ if base_style is not None:
+ style.update(base_style)
+ attribute_names = [
+ "alignment-baseline",
+ "baseline-shift",
+ "clip-path",
+ "clip-rule",
+ "color",
+ "color-interpolation",
+ "color-interpolation-filters",
+ "color-rendering",
+ "cursor",
+ "direction",
+ "display",
+ "dominant-baseline",
+ "fill",
+ "fill-opacity",
+ "fill-rule",
+ "filter",
+ "flood-color",
+ "flood-opacity",
+ "font-family",
+ "font-size",
+ "font-size-adjust",
+ "font-stretch",
+ "font-style",
+ "font-variant",
+ "font-weight",
+ "glyph-orientation-horizontal",
+ "glyph-orientation-vertical",
+ "image-rendering",
+ "letter-spacing",
+ "lighting-color",
+ "marker-end",
+ "marker-mid",
+ "marker-start",
+ "mask",
+ "opacity",
+ "overflow",
+ "paint-order",
+ "pointer-events",
+ "shape-rendering",
+ "stop-color",
+ "stop-opacity",
+ "stroke",
+ "stroke-dasharray",
+ "stroke-dashoffset",
+ "stroke-linecap",
+ "stroke-linejoin",
+ "stroke-miterlimit",
+ "stroke-opacity",
+ "stroke-width",
+ "text-anchor",
+ "text-decoration",
+ "text-overflow",
+ "text-rendering",
+ "unicode-bidi",
+ "vector-effect",
+ "visibility",
+ "white-space",
+ "word-spacing",
+ "writing-mode",
+ ]
+ for name in attribute_names:
+ value = node.getAttribute(name)
+ if value != "":
+ style[name] = value
+
+ for element in node.getAttribute("style").split(";"):
+ if element == "":
+ continue
+ key, value = element.split(":", 1)
+ style[key] = value
+ return style
+
+
+def get_css_style(node: minidom.Element, css_list: list, base_style: dict) -> dict:
+ """update the base_style with the style definitions from the stylesheet that are applicable to the node
+ defined by the classes or id of the node
+ """
+ style = {}
+ if base_style is not None:
+ style.update(base_style)
+ classes = node.getAttribute("class").split()
+ for css in css_list:
+ css_condition, css_style = css
+ if css_condition[0] == "." and css_condition[1:] in classes:
+ style.update(css_style)
+ elif css_condition[0] == "#" and css_condition[1:] == node.getAttribute("id"):
+ style.update(css_style)
+ elif css_condition == node.tagName:
+ style.update(css_style)
+ return style
+
+
+def apply_style(style: dict, patch: mpatches.Patch) -> dict:
+ """apply the properties defined in style to the given patch"""
+ fill_opacity = float(style.get("opacity", 1)) * float(style.get("fill-opacity", 1))
+ stroke_opacity = float(style.get("opacity", 1)) * float(
+ style.get("stroke-opacity", 1)
+ )
+
+ def readColor(value):
+ try:
+ return mcolors.to_rgb(value)
+ except:
+ # matplotlib cannot handle html colors in the form #000
+ if len(value) == 4 and value[0] == "#":
+ return readColor("#" + value[1] * 2 + value[2] * 2 + value[3] * 2)
+ raise
+
+ # matplotlib defaults differ
+ if "fill" not in style:
+ style["fill"] = "none"
+ if "stroke" not in style:
+ style["stroke"] = "none"
+
+ for key, value in style.items():
+ try:
+ if key == "opacity":
+ pass
+ # patch.set_alpha(float(value))
+ elif key == "fill":
+ if value == "none" or value == "transparent":
+ patch.set_facecolor("none")
+ else:
+ try:
+ r, g, b = readColor(value)
+ patch.set_facecolor((r, g, b, fill_opacity))
+ except Exception:
+ patch.set_facecolor("none")
+ raise
+ elif key == "fill-opacity":
+ pass
+ elif key == "stroke":
+ if value == "none" or value == "transparent":
+ patch.set_edgecolor("none")
+ else:
+ try:
+ r, g, b = readColor(value)
+ patch.set_edgecolor((r, g, b, stroke_opacity))
+ except Exception:
+ patch.set_edgecolor("none")
+ raise
+ elif key == "stroke-opacity":
+ pass
+ elif key == "stroke-dasharray":
+ if value != "none":
+ offset = 0
+ if isinstance(patch.get_linestyle(), tuple):
+ offset, dashes = patch.get_linestyle()
+ patch.set_linestyle(
+ (offset, [float(s) * 4 for s in value.split(",")])
+ )
+ elif key == "stroke-dashoffset":
+ dashes = [1, 0]
+ if isinstance(patch.get_linestyle(), tuple):
+ offset, dashes = patch.get_linestyle()
+ patch.set_linestyle((float(value) * 4, dashes))
+ elif key == "stroke-linecap":
+ if value == "square":
+ value = "projecting"
+ patch.set_capstyle(value)
+ elif key == "stroke-linejoin":
+ patch.set_joinstyle(value)
+ elif key == "stroke-miterlimit":
+ pass # unfortunately we cannot implement this in matplotlib
+ elif key == "stroke-width":
+ try:
+ patch.set_linewidth(svgUnitToMpl(value))
+ except AttributeError:
+ pass
+ elif key == "stroke-linecap":
+ try:
+ patch.set_dash_capstyle(value) # ty:ignore[unresolved-attribute]
+ patch.set_solid_capstyle(value) # ty:ignore[unresolved-attribute]
+ except AttributeError:
+ pass
+ elif key == "font-size":
+ pass
+ elif key == "font-weight":
+ pass
+ elif key == "font-style":
+ pass
+ elif key == "font-family":
+ pass
+ elif key == "font-variant":
+ pass
+ elif key == "font-stretch":
+ pass
+ elif key == "display":
+ pass
+ elif key == "text-anchor":
+ pass
+ else:
+ print("ERROR: unknown style key", key, file=sys.stderr)
+ except ValueError:
+ print("ERROR: could not set style", key, value, file=sys.stderr)
+ return style
+
+
+def font_properties_from_style(style: dict) -> FontProperties:
+ """convert a style to a FontProperties object"""
+ fp = FontProperties()
+ for key, value in style.items():
+ if key == "font-family":
+ fp.set_family(value)
+ if key == "font-size":
+ fp.set_size(svgUnitToMpl(value))
+ if key == "font-weight":
+ fp.set_weight(value)
+ if key == "font-style":
+ fp.set_style(value)
+ if key == "font-variant":
+ fp.set_variant(value)
+ if key == "font-stretch":
+ fp.set_stretch(value)
+ return fp
+
+
+def styleNoDisplay(style: dict) -> bool:
+ """check whether the style defines not to display the element"""
+ return (
+ style.get("display", "inline") == "none"
+ or style.get("visibility", "visible") == "hidden"
+ or style.get("visibility", "visible") == "collapse"
+ )
+
+
+def plt_patch(
+ node: minidom.Element,
+ trans_parent_trans: mtransforms.Transform,
+ style: dict,
+ constructor: Callable,
+ ids: dict,
+ no_draw: bool = False,
+) -> list[mpatches.Patch]:
+ """add a node to the figure by calling the provided constructor"""
+ trans_node = parseTransformation(node.getAttribute("transform"))
+ style = get_inline_style(node, get_css_style(node, ids["css"], style))
+
+ patch = constructor(
+ node, trans_node + trans_parent_trans + plt.gca().transData, style, ids
+ )
+ if not isinstance(patch, list):
+ patch = [patch]
+
+ for p in patch:
+ if not getattr(p, "is_marker", False):
+ style = apply_style(style, p)
+ p.style = style
+ # p.set_transform(p.get_transform() + plt.gca().transData)
+ p.trans_parent = trans_parent_trans
+ p.trans_node = parseTransformation(node.getAttribute("transform"))
+
+ if not no_draw and not styleNoDisplay(style):
+ plt.gca().add_patch(p)
+ if node.getAttribute("id") != "":
+ ids[node.getAttribute("id")] = patch
+ return patch
+
+
+def clone_patch(patch: mpatches.Patch) -> mpatches.Patch:
+ """clone a patch element with the same properties as the given patch"""
+ if isinstance(patch, mpatches.Rectangle):
+ return mpatches.Rectangle(
+ xy=patch.get_xy(), width=patch.get_width(), height=patch.get_height()
+ )
+ if isinstance(patch, mpatches.Circle):
+ return mpatches.Circle(xy=patch.center, radius=patch.get_radius()) # ty:ignore[invalid-argument-type] - matplotlib stubs incorrectly type center as float
+ if isinstance(patch, mpatches.Ellipse):
+ return mpatches.Ellipse(
+ xy=patch.center, # ty:ignore[invalid-argument-type] - matplotlib stubs incorrectly type center as float
+ width=patch.get_width(),
+ height=patch.get_height(),
+ )
+ if isinstance(patch, mpatches.PathPatch):
+ return mpatches.PathPatch(patch.get_path())
+ raise TypeError("unknown patch type")
+
+
+def patch_rect(
+ node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict
+) -> mpatches.Patch | list[mpatches.Patch]:
+ """draw a svg rectangle node as a rectangle patch element into the figure (with the given transformation and style)"""
+ if node.getAttribute("d") != "":
+ return patch_path(node, trans, style, ids)
+ if node.getAttribute("ry") != "" and node.getAttribute("ry") != 0:
+ return mpatches.FancyBboxPatch(
+ xy=(float(node.getAttribute("x")), float(node.getAttribute("y"))),
+ width=float(node.getAttribute("width")),
+ height=float(node.getAttribute("height")),
+ boxstyle=mpatches.BoxStyle.Round(
+ 0, rounding_size=float(node.getAttribute("ry"))
+ ),
+ transform=trans,
+ )
+ return mpatches.Rectangle(
+ xy=(float(node.getAttribute("x")), float(node.getAttribute("y"))),
+ width=float(node.getAttribute("width")),
+ height=float(node.getAttribute("height")),
+ transform=trans,
+ )
+
+
+def patch_ellipse(
+ node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict
+) -> mpatches.Patch | list[mpatches.Patch]:
+ """draw a svg ellipse node as a ellipse patch element into the figure (with the given transformation and style)"""
+ if node.getAttribute("d") != "":
+ return patch_path(node, trans, style, ids)
+ return mpatches.Ellipse(
+ xy=(float(node.getAttribute("cx")), float(node.getAttribute("cy"))),
+ width=float(node.getAttribute("rx")) * 2,
+ height=float(node.getAttribute("ry")) * 2,
+ transform=trans,
+ )
+
+
+def patch_circle(
+ node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict
+) -> mpatches.Patch | list[mpatches.Patch]:
+ """draw a svg circle node as a circle patch element into the figure (with the given transformation and style)"""
+ if node.getAttribute("d") != "":
+ return patch_path(node, trans, style, ids)
+ return mpatches.Circle(
+ xy=(float(node.getAttribute("cx")), float(node.getAttribute("cy"))),
+ radius=float(node.getAttribute("r")),
+ transform=trans,
+ )
+
+
+def plt_draw_text(
+ node: minidom.Element,
+ trans: mtransforms.Transform,
+ style: dict,
+ ids: dict,
+ no_draw: bool = False,
+):
+ """draw a svg text node as a text patch element into the figure (with the given transformation and style)"""
+ trans = (
+ parseTransformation(node.getAttribute("transform"))
+ + trans
+ + plt.gca().transData
+ )
+ trans = mtransforms.Affine2D([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) + trans
+ if node.getAttribute("x") != "":
+ pos = np.array(
+ [
+ svgUnitToMpl(node.getAttribute("x")),
+ -svgUnitToMpl(node.getAttribute("y")),
+ ]
+ )
+ else:
+ pos = np.array([0, 0])
+
+ style = get_inline_style(node, get_css_style(node, ids["css"], style))
+
+ dx = node.getAttribute("dx") or "0"
+ dy = node.getAttribute("dy") or "0"
+
+ text_content = ""
+ patch_list = []
+ for child in node.childNodes:
+ if not isinstance(child, minidom.Element):
+ partial_content = child.data
+ pos_child = pos.copy()
+
+ pos_child[0] += svgUnitToMpl(dx)
+ pos_child[1] -= svgUnitToMpl(dy)
+ style_child = style
+ part_id = ""
+ else:
+ part_id = node.getAttribute("id")
+ if child.firstChild is None:
+ continue
+ partial_content = child.firstChild.nodeValue or ""
+ style_child = get_inline_style(
+ child, get_css_style(child, ids["css"], style)
+ )
+ pos_child = pos.copy()
+ if child.getAttribute("x") != "":
+ pos_child = np.array(
+ [
+ svgUnitToMpl(child.getAttribute("x")),
+ -svgUnitToMpl(child.getAttribute("y")),
+ ]
+ )
+ if child.getAttribute("dx") != "":
+ pos_child[0] += svgUnitToMpl(child.getAttribute("dx"))
+ if child.getAttribute("dy") != "":
+ pos_child[1] -= svgUnitToMpl(child.getAttribute("dy"))
+
+ text_content += partial_content
+ path1 = TextPath(
+ pos_child,
+ partial_content,
+ prop=font_properties_from_style(style_child),
+ )
+ patch = mpatches.PathPatch(path1, transform=trans)
+
+ apply_style(style_child, patch)
+ if not no_draw and not styleNoDisplay(style_child):
+ plt.gca().add_patch(patch)
+ if part_id != "":
+ ids[part_id] = patch
+ patch_list.append(patch)
+
+ if node.getAttribute("id") != "":
+ ids[node.getAttribute("id")] = patch_list
+
+
+def patch_path(
+ node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict
+) -> list:
+ """draw a path svg node by using a matplotlib path patch (with the given transform and style)"""
+
+ start_pos = None
+ command = None
+ verts = []
+ codes = []
+ angles = []
+
+ current_pos = np.array([0, 0])
+
+ elements = [
+ a[0]
+ for a in re.findall(
+ r"(([-+]?\d*\.?\d+(?:e[-+]?\d*\.?\d+)?)|\w)", node.getAttribute("d")
+ )
+ ]
+ elements.reverse()
+
+ def popPos():
+ pos = np.array([float(elements.pop()), float(elements.pop())])
+ if not absolute:
+ pos += current_pos
+ return pos
+
+ def popValue(count=None):
+ if count is None:
+ return float(elements.pop())
+ else:
+ return [float(elements.pop()) for i in range(count)]
+
+ def addPathElement(type, *positions, no_angle=False):
+ for pos in positions:
+ verts.append(pos)
+ codes.append(type)
+
+ def vec2angle(vec):
+ return np.arctan2(vec[1], vec[0])
+
+ if not no_angle:
+ n = len(positions)
+ angles[-1].append(vec2angle(verts[-n] - verts[-n - 1]))
+ for i in range(n - 1):
+ angles.append([])
+ angles.append([vec2angle(verts[-1] - verts[-2])])
+ else:
+ angles.append([])
+ return positions[-1]
+
+ i = len(elements)
+ while elements:
+ # if things go wrong for some reason prevent endless loops
+ i -= 1
+ if i <= 0:
+ break
+ if "A" <= elements[-1] <= "z":
+ last_command = command
+ command = elements.pop()
+ absolute = command.isupper()
+ command = command.lower()
+
+ # moveto
+ if command == "m":
+ current_pos = addPathElement(mpath.Path.MOVETO, popPos(), no_angle=True)
+ start_pos = current_pos
+
+ command = "l"
+ # close
+ elif command == "z":
+ # Close path
+ current_pos = addPathElement(mpath.Path.CLOSEPOLY, start_pos, no_angle=True)
+
+ start_pos = None
+ command = None # You can't have implicit commands after closing.
+ # lineto
+ elif command == "l":
+ current_pos = addPathElement(mpath.Path.LINETO, popPos())
+
+ # horizontal lineto
+ elif command == "h":
+ current_pos = addPathElement(
+ mpath.Path.LINETO,
+ np.array(
+ [popValue() + current_pos[0] * (1 - absolute), current_pos[1]]
+ ),
+ )
+ # vertical lineto
+ elif command == "v":
+ current_pos = addPathElement(
+ mpath.Path.LINETO,
+ np.array(
+ [current_pos[0], popValue() + current_pos[1] * (1 - absolute)]
+ ),
+ )
+ # cubic bezier curveto
+ elif command == "c":
+ current_pos = addPathElement(
+ mpath.Path.CURVE4, popPos(), popPos(), popPos()
+ )
+
+ # smooth cubic bezier curveto
+ elif command == "s":
+ # Smooth curve. First control point is the "reflection" of
+ # the second control point in the previous path.
+
+ if last_command not in "cs": # ty:ignore[unsupported-operator]
+ # If there is no previous command or if the previous command
+ # was not an C, c, S or s, assume the first control point is
+ # coincident with the current point.
+ control1 = current_pos
+ else:
+ # The first control point is assumed to be the reflection of
+ # the second control point on the previous command relative
+ # to the current point.
+ control1 = current_pos + (current_pos - verts[-2])
+
+ current_pos = addPathElement(
+ mpath.Path.CURVE4, control1, popPos(), popPos()
+ )
+
+ # quadratic bezier curveto
+ elif command == "q":
+ current_pos = addPathElement(mpath.Path.CURVE3, popPos(), popPos())
+
+ # smooth quadratic bezier curveto
+ elif command == "t":
+ # Smooth curve. Control point is the "reflection" of
+ # the second control point in the previous path.
+
+ if last_command not in "qt": # ty:ignore[unsupported-operator]
+ # If there is no previous command or if the previous command
+ # was not an Q, q, T or t, assume the first control point is
+ # coincident with the current point.
+ control1 = current_pos
+ else:
+ # The control point is assumed to be the reflection of
+ # the control point on the previous command relative
+ # to the current point.
+ control1 = current_pos + (current_pos - verts[-2])
+ current_pos = addPathElement(mpath.Path.CURVE3, control1, popPos())
+
+ # elliptical arc
+ elif command == "a":
+ radius1, radius2, rotation, arc, sweep = popValue(5)
+ end = popPos()
+
+ current_pos = addPathElement(
+ mpath.Path.CURVE4,
+ *arcToBezier(current_pos, end, radius1, radius2, rotation, arc, sweep),
+ )
+
+ # average angles when a point has more than one line
+ for i in range(len(angles)):
+ if len(angles[i]) == 1:
+ angles[i] = angles[i][0]
+ else:
+ angles[i] = np.arctan2(
+ np.mean(np.sin(angles[i])), np.mean(np.cos(angles[i]))
+ )
+
+ def addMarker(i, name):
+ marker_style, patches = ids[name]
+
+ def add_list_elements(element):
+ if isinstance(element, list):
+ for e in element:
+ add_list_elements(e)
+ else:
+ parent_patch = element
+ patch = clone_patch(parent_patch)
+
+ apply_style(parent_patch.style, patch)
+
+ a = angles[i]
+ ca, sa = np.cos(a), np.sin(a)
+ ox, oy = verts[i]
+ trans2 = (
+ parent_patch.trans_node
+ + mtransforms.Affine2D([[ca, -sa, ox], [sa, ca, oy], [0, 0, 1]])
+ + parent_patch.trans_parent
+ + trans
+ ) # + plt.gca().transAxes
+ if marker_style.get("markerUnits", "strokeWidth") == "strokeWidth":
+ s = svgUnitToMpl(style["stroke-width"])
+ trans2 = (
+ mtransforms.Affine2D([[s, 0, 0], [0, s, 0], [0, 0, 1]]) + trans2
+ )
+ patch.set_transform(trans2)
+ patch.is_marker = True # ty:ignore[unresolved-attribute]
+ patch_list.append(patch)
+
+ add_list_elements(patches)
+
+ patch_list = []
+
+ if len(verts) == 0:
+ return patch_list
+ path = mpath.Path(verts, codes)
+ patch_list.append(mpatches.PathPatch(path, transform=trans))
+
+ marker_start = style.get("marker-start")
+ if marker_start and marker_start.startswith("url(#"):
+ name = marker_start[len("url(#") : -1]
+ if name in ids:
+ addMarker(0, name)
+ marker_mid = style.get("marker-mid")
+ if marker_mid and marker_mid.startswith("url(#"):
+ name = marker_mid[len("url(#") : -1]
+ if name in ids:
+ for i in range(1, len(angles) - 1):
+ addMarker(i, name)
+ marker_end = style.get("marker-end")
+ if marker_end and marker_end.startswith("url(#"):
+ name = marker_end[len("url(#") : -1]
+ if name in ids:
+ addMarker(len(angles) - 1, name)
+
+ return patch_list
+
+
+def svgUnitToMpl(unit: str, default: float = 0) -> float:
+ """convert a unit text to svg pixels"""
+ import re
+
+ if unit == "":
+ return default
+ match = re.match(r"^([-.\d]*)(\w*).*$", unit)
+ if match:
+ value, unit = match.groups()
+ value = float(value)
+ if unit == "pt":
+ value *= plt.gcf().dpi / 72
+ elif unit == "pc":
+ value *= getattr(plt.gcf(), "dpi", 100) / 6
+ elif unit == "in":
+ value *= getattr(plt.gcf(), "dpi", 100)
+ elif unit == "px":
+ pass
+ elif unit == "cm":
+ value *= getattr(plt.gcf(), "dpi", 100) / 2.5
+ elif unit == "mm":
+ value *= getattr(plt.gcf(), "dpi", 100) / 25
+ return value
+ return default
+
+
+def openImageFromLink(link: str) -> np.ndarray:
+ """load an embedded image file or an externally liked image file"""
+ if link.startswith("file:///"):
+ return plt.imread(link[len("file:///") :])
+ else:
+ match = re.match(r"data:image/(\w*);base64,(.*)", link)
+ if match is None:
+ raise ValueError(f"Invalid image link format: {link}")
+ type, data = match.groups()
+
+ data = base64.decodebytes(bytes(data, "utf-8"))
+
+ buf = io.BytesIO()
+ buf.write(data)
+ buf.seek(0)
+ im = plt.imread(buf, format=type)
+ buf.close()
+ return im
+
+
+def parseStyleSheet(text: str) -> list:
+ """parse a style sheet text"""
+ # remove line comments
+ text = re.sub("//.*?\n", "", text)
+ # remove multiline comments
+ text = text.replace("\n", " ")
+ text = re.sub(r"/\*.*?\*/", "", text)
+ text = re.sub(r"/\*.*?\*/", "", text)
+
+ style_definitions = []
+ styles = re.findall("[^}]*{[^}]*}", text)
+ for style in styles:
+ condition, main = style.split("{", 1)
+ parts = [
+ part.strip().split(":", 1)
+ for part in main[:-1].split(";")
+ if part.strip() != ""
+ ]
+ style_dict = {k: v.strip() for k, v in parts}
+ for cond in condition.split(","):
+ style_definitions.append([cond.strip(), style_dict])
+ return style_definitions
+
+
+def parseGroup(
+ node: minidom.Element,
+ trans: mtransforms.Transform,
+ style: dict,
+ ids: dict,
+ no_draw: bool = False,
+) -> list:
+ """parse the children of a group node with the inherited transformation and style"""
+ trans = parseTransformation(node.getAttribute("transform")) + trans
+ style = get_inline_style(node, style)
+
+ patch_list = []
+ for child in node.childNodes:
+ if child.nodeType == child.TEXT_NODE or child.nodeType == child.COMMENT_NODE:
+ continue
+ if not isinstance(child, minidom.Element):
+ continue
+ if child.tagName == "style":
+ for childchild in child.childNodes:
+ if childchild.nodeType == childchild.CDATA_SECTION_NODE:
+ ids["css"].extend(parseStyleSheet(childchild.wholeText))
+ elif child.tagName == "rect":
+ patch_list.append(
+ plt_patch(child, trans, style, patch_rect, ids, no_draw=no_draw)
+ )
+ elif child.tagName == "ellipse":
+ patch_list.append(
+ plt_patch(child, trans, style, patch_ellipse, ids, no_draw=no_draw)
+ )
+ elif child.tagName == "circle":
+ patch_list.append(
+ plt_patch(child, trans, style, patch_circle, ids, no_draw=no_draw)
+ )
+ elif child.tagName == "path":
+ patch_list.append(
+ plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw)
+ )
+ elif child.tagName == "polygon":
+ # matplotlib has a designated polygon patch, but it is easier to just convert it to a path
+ child.setAttribute("d", "M " + child.getAttribute("points") + " Z")
+ patch_list.append(
+ plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw)
+ )
+ elif child.tagName == "polyline":
+ child.setAttribute("d", "M " + child.getAttribute("points"))
+ patch_list.append(
+ plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw)
+ )
+ elif child.tagName == "line":
+ child.setAttribute(
+ "d",
+ "M "
+ + child.getAttribute("x1")
+ + ","
+ + child.getAttribute("y1")
+ + " "
+ + child.getAttribute("x2")
+ + ","
+ + child.getAttribute("y2"),
+ )
+ patch_list.append(
+ plt_patch(child, trans, style, patch_path, ids, no_draw=no_draw)
+ )
+ elif child.tagName == "g":
+ patch_list.append(
+ parseGroup(
+ child, trans, style, ids, no_draw=(no_draw or styleNoDisplay(style))
+ )
+ )
+ elif child.tagName == "text":
+ patch_list.append(plt_draw_text(child, trans, style, ids, no_draw=no_draw))
+ elif child.tagName == "defs":
+ patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
+ elif child.tagName == "clipPath":
+ patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
+ elif child.tagName == "symbol":
+ patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
+ elif child.tagName == "marker":
+ patch_list.append(parseGroup(child, trans, style, ids, no_draw=True))
+ elif child.tagName == "sodipodi:namedview":
+ pass # used for some inkscape metadata
+ elif child.tagName == "image":
+ link = child.getAttribute("xlink:href")
+ im = openImageFromLink(link)
+ if no_draw is False:
+ if child.getAttribute("x") != "":
+ plt.imshow(
+ im[::-1],
+ extent=[
+ svgUnitToMpl(child.getAttribute("x")),
+ svgUnitToMpl(child.getAttribute("x"))
+ + svgUnitToMpl(child.getAttribute("width")),
+ svgUnitToMpl(child.getAttribute("y")),
+ svgUnitToMpl(child.getAttribute("y"))
+ + svgUnitToMpl(child.getAttribute("height")),
+ ],
+ zorder=1,
+ )
+ else:
+ pass # im_patch = plt.imshow(im[::-1], zorder=1)
+ # patch_list.append(im_patch)
+ elif child.tagName == "metadata":
+ pass # we do not have to draw metadata
+ else:
+ print("Unknown tag", child.tagName, file=sys.stderr)
+
+ if node.getAttribute("id") != "":
+ ids[node.getAttribute("id")] = [style, patch_list]
+
+ return patch_list
+
+
+def svgread(filename: str):
+ """read an SVG file"""
+ doc = minidom.parse(filename)
+
+ svg = doc.getElementsByTagName("svg")[0]
+ try:
+ x1, y1, x2, y2 = [
+ svgUnitToMpl(s.strip()) for s in svg.getAttribute("viewBox").split()
+ ]
+ width, height = (x2 - x1) / plt.gcf().dpi, (y2 - y1) / plt.gcf().dpi
+ if max([width, height]) > 8:
+ f = 8 / max([width, height])
+ plt.gcf().set_size_inches(width * f, height * f)
+ else:
+ plt.gcf().set_size_inches(width, height)
+ except ValueError:
+ width = svgUnitToMpl(svg.getAttribute("width"), default=100)
+ height = svgUnitToMpl(svg.getAttribute("height"), default=100)
+ x1, y1, x2, y2 = 0, 0, width, height
+ width /= plt.gcf().dpi
+ height /= plt.gcf().dpi
+ if max([width, height]) > 8:
+ f = 8 / max([width, height])
+ plt.gcf().set_size_inches(width * f, height * f)
+ else:
+ plt.gcf().set_size_inches(width, height)
+ ax = plt.axes((0, 0, 1, 1), label=filename, frameon=False)
+ plt.xticks([])
+ plt.yticks([])
+ for spine in ["left", "right", "top", "bottom"]:
+ ax.spines[spine].set_visible(False)
+ plt.xlim(x1, x2)
+ plt.ylim(y2, y1)
+
+ parseGroup(
+ doc.getElementsByTagName("svg")[0],
+ mtransforms.IdentityTransform(),
+ {},
+ {"css": []},
+ )
diff --git a/pylustrator/py.typed b/pylustrator/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/pylustrator/pyjack.py b/pylustrator/pyjack.py
index 270fc45..4f3bf1c 100644
--- a/pylustrator/pyjack.py
+++ b/pylustrator/pyjack.py
@@ -12,12 +12,15 @@
NOTE: adapted for pylustrator to be Python3 compatible
"""
-import sys as _sys
+
import gc as _gc
import types as _types
import inspect as _inspect
-_WRAPPER_TYPES = (type(object.__init__), type(object().__init__),)
+_WRAPPER_TYPES = (
+ type(object.__init__),
+ type(object().__init__),
+)
# deactivated closure support as this code does not work for python3
"""
@@ -30,7 +33,9 @@ def proxy1(): return data
_CELLTYPE = int # type(proxy0(None).func_closure[0])
"""
-class PyjackException(Exception): pass
+
+class PyjackException(Exception):
+ pass
def connect(fn, proxyfn):
@@ -58,26 +63,29 @@ def connect(fn, proxyfn):
return _PyjackFuncCode(fn, proxyfn)._fn
elif issubclass(fn_type, _types.MethodType):
return _PyjackFuncCode(fn.im_func, proxyfn)._fn
- elif issubclass(fn_type, (_types.BuiltinFunctionType,
- _types.BuiltinMethodType,
- type)):
- return _PyjackFuncBuiltin(fn, proxyfn)
- elif _sys.version_info < (2, 5) and issubclass(fn_type, type(file)):
- # in python 2.4, open is of type file, not :class:`types.FunctionType`
+ elif issubclass(
+ fn_type, (_types.BuiltinFunctionType, _types.BuiltinMethodType, type)
+ ):
return _PyjackFuncBuiltin(fn, proxyfn)
+ # elif _sys.version_info < (2, 5) and issubclass(fn_type, type(file)):
+ # # in python 2.4, open is of type file, not :class:`types.FunctionType`
+ # return _PyjackFuncBuiltin(fn, proxyfn)
elif issubclass(fn_type, _WRAPPER_TYPES):
raise PyjackException("Wrappers not supported. Make a concrete fn.")
- elif isinstance(getattr(fn, '__call__', None), _types.MethodType):
+ elif isinstance(getattr(fn, "__call__", None), _types.MethodType):
_PyjackFuncCode(fn.__call__.im_func, proxyfn)
def restore():
fn.__call__.im_func.restore()
- delattr(fn, 'restore')
+ delattr(fn, "restore")
fn.restore = restore
return fn
else:
- bundle = (fn, fn_type,)
+ bundle = (
+ fn,
+ fn_type,
+ )
raise PyjackException("fn %r of type '%r' not supported" % bundle)
@@ -150,20 +158,18 @@ def replace_all_refs(org_obj, new_obj):
_gc.collect()
- hit = False
+ # hit = False
for referrer in _gc.get_referrers(org_obj):
-
# FRAMES -- PASS THEM UP
if isinstance(referrer, _types.FrameType):
continue
# DICTS
if isinstance(referrer, dict):
-
cls = None
# THIS CODE HERE IS TO DEAL WITH DICTPROXY TYPES
- if '__dict__' in referrer and '__weakref__' in referrer:
+ if "__dict__" in referrer and "__weakref__" in referrer:
for cls in _gc.get_referrers(referrer):
if _inspect.isclass(cls) and cls.__dict__ == referrer:
break
@@ -171,14 +177,14 @@ def replace_all_refs(org_obj, new_obj):
for key, value in referrer.items():
# REMEMBER TO REPLACE VALUES ...
if value is org_obj:
- hit = True
+ # hit = True
value = new_obj
referrer[key] = value
if cls: # AGAIN, CLEANUP DICTPROXY PROBLEM
setattr(cls, key, new_obj)
# AND KEYS.
if key is org_obj:
- hit = True
+ # hit = True
del referrer[key]
referrer[new_obj] = value
@@ -186,17 +192,23 @@ def replace_all_refs(org_obj, new_obj):
elif isinstance(referrer, list):
for i, value in enumerate(referrer):
if value is org_obj:
- hit = True
+ # hit = True
referrer[i] = new_obj
# SETS
elif isinstance(referrer, set):
referrer.remove(org_obj)
referrer.add(new_obj)
- hit = True
+ # hit = True
# TUPLE, FROZENSET
- elif isinstance(referrer, (tuple, frozenset,)):
+ elif isinstance(
+ referrer,
+ (
+ tuple,
+ frozenset,
+ ),
+ ):
new_tuple = []
for obj in referrer:
if obj is org_obj:
@@ -206,7 +218,7 @@ def replace_all_refs(org_obj, new_obj):
replace_all_refs(referrer, type(referrer)(new_tuple))
# CELLTYPE (deactivated as it makes problems im Python3)
- #elif isinstance(referrer, _CELLTYPE):
+ # elif isinstance(referrer, _CELLTYPE):
# def proxy0(data):
# def proxy1(): return data
@@ -219,26 +231,30 @@ def replace_all_refs(org_obj, new_obj):
# FUNCTIONS
elif isinstance(referrer, _types.FunctionType):
localsmap = {}
- for key in ['func_code', 'func_globals', 'func_name',
- 'func_defaults', 'func_closure']:
+ for key in [
+ "func_code",
+ "func_globals",
+ "func_name",
+ "func_defaults",
+ "func_closure",
+ ]:
orgattr = getattr(referrer, key)
if orgattr is org_obj:
- localsmap[key.split('func_')[-1]] = new_obj
+ localsmap[key.split("func_")[-1]] = new_obj
else:
- localsmap[key.split('func_')[-1]] = orgattr
- localsmap['argdefs'] = localsmap['defaults']
- del localsmap['defaults']
+ localsmap[key.split("func_")[-1]] = orgattr
+ localsmap["argdefs"] = localsmap["defaults"]
+ del localsmap["defaults"]
newfn = _types.FunctionType(**localsmap)
replace_all_refs(referrer, newfn)
# OTHER (IN DEBUG, SEE WHAT IS NOT SUPPORTED).
else:
# debug:
- import sys
# print(type(referrer), file=sys.stderr)
pass
- #if hit is False:
+ # if hit is False:
# raise AttributeError("Object '%r' not found" % org_obj)
return org_obj
@@ -251,29 +267,30 @@ def _get_self():
global _func_code_map
frame = _inspect.currentframe()
- code = frame.f_back.f_code
+ code = frame.f_back.f_code # ty:ignore[possibly-missing-attribute]
return _func_code_map[code]
-class _PyjackFunc(object): pass
+class _PyjackFunc(object):
+ pass
class _PyjackFuncCode(_PyjackFunc):
-
def __init__(self, fn, proxyfn):
global _func_code_map
self._fn, self._proxyfn = fn, proxyfn
def proxy(*args, **kwargs):
- import pyjack
+ import pyjack # ty:ignore[unresolved-import]
+
self = pyjack._get_self()
return self._process_fn(args, kwargs)
- _func_code_map[proxy.func_code] = self
+ _func_code_map[proxy.func_code] = self # ty:ignore[unresolved-attribute]
self._org_func_code = fn.func_code
- self._proxy_func_code = proxy.func_code
- fn.func_code = proxy.func_code
+ self._proxy_func_code = proxy.func_code # ty:ignore[unresolved-attribute]
+ fn.func_code = proxy.func_code # ty:ignore[unresolved-attribute]
fn.restore = self.restore
@@ -288,7 +305,6 @@ def restore(self):
class _PyjackFuncBuiltin(_PyjackFunc):
-
def __init__(self, fn, proxyfn):
self._fn = replace_all_refs(fn, self)
self._proxyfn = proxyfn
@@ -300,15 +316,17 @@ def __getattr__(self, attr):
try:
return getattr(self._fn, attr)
except AttributeError:
- bundle = (self._fn, attr,)
+ bundle = (
+ self._fn,
+ attr,
+ )
raise AttributeError("function %r has no attr '%s'" % bundle)
def restore(self):
replace_all_refs(self, self._fn)
-if __name__ == '__main__':
+if __name__ == "__main__":
import doctest
doctest.testmod(optionflags=524)
-
diff --git a/pylustrator/snap.py b/pylustrator/snap.py
index b578b82..8132b3e 100644
--- a/pylustrator/snap.py
+++ b/pylustrator/snap.py
@@ -1,665 +1,819 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# snap.py
-
-# Copyright (c) 2016-2020, Richard Gerum
-#
-# This file is part of Pylustrator.
-#
-# Pylustrator is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pylustrator is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pylustrator. If not, see
-
-from typing import List, Tuple, Optional
-from packaging import version
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-
-import matplotlib as mpl
-import matplotlib.pyplot as plt
-import numpy as np
-from matplotlib.artist import Artist
-try: # starting from mpl version 3.6.0
- from matplotlib.axes import Axes
-except:
- from matplotlib.axes._subplots import Axes
-from matplotlib.legend import Legend
-from matplotlib.lines import Line2D
-from matplotlib.patches import Rectangle, Ellipse, FancyArrowPatch
-from matplotlib.text import Text
-from matplotlib.figure import Figure
-try:
- from matplotlib.figure import SubFigure # since matplotlib 3.4.0
-except ImportError:
- SubFigure = None
-from .helper_functions import main_figure
-
-
-DIR_X0 = 1
-DIR_Y0 = 2
-DIR_X1 = 4
-DIR_Y1 = 8
-
-
-def checkXLabel(target: Artist):
- """ checks if the target is the xlabel of an axis """
- for axes in target.figure.axes:
- if axes.xaxis.get_label() == target:
- return axes
-
-
-def checkYLabel(target: Artist):
- """ checks if the target is the ylabel of an axis """
- for axes in target.figure.axes:
- if axes.yaxis.get_label() == target:
- return axes
-
-
-def cache_property(object, name):
- if getattr(object, f"_pylustrator_cached_{name}", False) is True:
- return
- setattr(object, f"_pylustrator_cached_{name}", True)
- getter = getattr(object, f"get_{name}")
- setter = getattr(object, f"set_{name}")
- def new_getter(*args, **kwargs):
- if getattr(object, f"_pylustrator_cache_{name}", None) is None:
- setattr(object, f"_pylustrator_cache_{name}", getter(*args, **kwargs))
- return getattr(object, f"_pylustrator_cache_{name}", None)
- def new_setter(*args, **kwargs):
- result = setter(*args, **kwargs)
- setattr(object, f"_pylustrator_cache_{name}", None)
- return result
- setattr(object, f"get_{name}", new_getter)
- setattr(object, f"set_{name}", new_setter)
-
-
-
-class TargetWrapper(object):
- """ a wrapper to add unified set and get position methods for any matplotlib artist """
- target = None
-
- def __init__(self, target: Artist):
- self.target = target
- self.figure = target.figure
- self.do_scale = True
- self.fixed_aspect = False
- # a patch uses the data_transform
- if isinstance(self.target, mpl.patches.Patch):
- self.get_transform = self.target.get_data_transform
- # axes use the figure_transform
- elif isinstance(self.target, Axes):
- # and optionally have a fixed aspect ratio
- if self.target.get_aspect() != "auto" and self.target.get_adjustable() != "datalim":
- self.fixed_aspect = True
- # old matplotlib version
- if version.parse(mpl.__version__) < version.parse("3.4.0"):
- self.get_transform = lambda: self.target.figure.transFigure
- else:
- self.get_transform = lambda: self.target.figure.transSubfigure if self.target.figure.transSubfigure else self.target.figure.transFigure
-
- # cache the get_position
- cache_property(self.target, "position")
- # texts use get_transform
- elif isinstance(self.target, Text):
- if getattr(self.target, "xy", None) is not None:
- self.do_scale = True
- else:
- self.do_scale = False
- if checkXLabel(self.target):
- self.label_factor = self.figure.dpi / 72.0
- if getattr(self.target, "pad_offset", None) is None:
- self.target.pad_offset = self.target.get_position()[1] - checkXLabel(
- self.target).xaxis.labelpad * self.label_factor
- self.label_y = self.target.get_position()[1]
- elif checkYLabel(self.target):
- self.label_factor = self.figure.dpi / 72.0
- if getattr(self.target, "pad_offset", None) is None:
- self.target.pad_offset = self.target.get_position()[0] - checkYLabel(
- self.target).yaxis.labelpad * self.label_factor
- self.label_x = self.target.get_position()[0]
- self.get_transform = self.target.get_transform
- # the default is to use get_transform
- else:
- self.get_transform = self.target.get_transform
- self.do_scale = False
-
- def get_positions(self, use_previous_offset=False, update_offset=False) -> (int, int, int, int):
- """ get the current position of the target Artist """
- points = []
- if isinstance(self.target, Rectangle):
- points.append(self.target.get_xy())
- p2 = (self.target.get_x() + self.target.get_width(), self.target.get_y() + self.target.get_height())
- points.append(p2)
- elif isinstance(self.target, Ellipse):
- c = self.target.center
- w = self.target.width
- h = self.target.height
- points.append((c[0] - w / 2, c[1] - h / 2))
- points.append((c[0] + w / 2, c[1] + h / 2))
- elif isinstance(self.target, FancyArrowPatch):
- points.append(self.target._posA_posB[0])
- points.append(self.target._posA_posB[1])
- points.extend(self.target.get_path().vertices)
- elif isinstance(self.target, Text):
- points.append(self.target.get_position())
- if checkXLabel(self.target):
- points[0] = (points[0][0], self.label_y)
- elif checkYLabel(self.target):
- points[0] = (self.label_x, points[0][1])
- if getattr(self.target, "xy", None) is not None:
- points.append(self.target.xy)
- bbox = self.target.get_bbox_patch()
- if bbox:
- points.append(bbox.get_transform().transform((bbox.get_x(), bbox.get_y())))
- points.append(
- bbox.get_transform().transform((bbox.get_x() + bbox.get_width(), bbox.get_y() + bbox.get_height())))
- points[-2:] = self.transform_inverted_points(points[-2:])
- if use_previous_offset is True:
- points[2] = points[0] + self.target._pylustrator_offset + points[2] - points[1]
- points[1] = points[0] + self.target._pylustrator_offset
- else:
- if getattr(self.target, "_pylustrator_offset", None) is None or update_offset:
- self.target._pylustrator_offset = np.array(points[1]) - np.array(points[0])
- elif isinstance(self.target, Axes):
- p1, p2 = np.array(self.target.get_position())
- points.append(p1)
- points.append(p2)
- elif isinstance(self.target, SubFigure):
- p1 = [self.target.bbox.x0, self.target.bbox.y0]
- p2 = [self.target.bbox.x1, self.target.bbox.y1]
- points.append(p1)
- points.append(p2)
- elif isinstance(self.target, Legend):
- bbox = self.target.get_frame().get_bbox()
- if isinstance(self.target.axes, Axes):
- transform = self.target.axes.transAxes
- elif isinstance(self.target.figure, Figure):
- transform = self.target.figure.transFigure
- else:
- transform = self.target.figure.transSubfigure
- if isinstance(self.target._get_loc(), int):
- # if the legend doesn't have a location yet, use the left bottom corner of the bounding box
- self.target._set_loc(tuple(transform.inverted().transform(tuple([bbox.x0, bbox.y0]))))
- points.append(transform.transform(self.target._get_loc()))
- # add points to span bounding box around the frame
- points.append([bbox.x0, bbox.y0])
- points.append([bbox.x1, bbox.y1])
- if use_previous_offset is True:
- points[2] = points[0] + self.target._pylustrator_offset + points[2] - points[1]
- points[1] = points[0] + self.target._pylustrator_offset
- else:
- if getattr(self.target, "_pylustrator_offset", None) is None or update_offset:
- self.target._pylustrator_offset = points[1] - points[0]
- return self.transform_points(points)
-
- def set_positions(self, points: (int, int)):
- """ set the position of the target Artist """
- points = self.transform_inverted_points(points)
-
- if self.figure.figure is not None:
- change_tracker = self.figure.figure.change_tracker
- else:
- change_tracker = self.figure.change_tracker
-
- if isinstance(self.target, Rectangle):
- self.target.set_xy(points[0])
- self.target.set_width(points[1][0] - points[0][0])
- self.target.set_height(points[1][1] - points[0][1])
- if self.target.get_label() is None or not self.target.get_label().startswith("_rect"):
- change_tracker.addChange(self.target, ".set_xy([%f, %f])" % tuple(self.target.get_xy()))
- change_tracker.addChange(self.target, ".set_width(%f)" % self.target.get_width())
- change_tracker.addChange(self.target, ".set_height(%f)" % self.target.get_height())
- elif isinstance(self.target, Ellipse):
- self.target.center = np.mean(points, axis=0)
- self.target.width = points[1][0] - points[0][0]
- self.target.height = points[1][1] - points[0][1]
- change_tracker.addChange(self.target, ".center = (%f, %f)" % tuple(self.target.center))
- change_tracker.addChange(self.target, ".width = %f" % self.target.width)
- change_tracker.addChange(self.target, ".height = %f" % self.target.height)
- elif isinstance(self.target, FancyArrowPatch):
- self.target.set_positions(points[0], points[1])
- change_tracker.addChange(self.target,
- ".set_positions(%s, %s)" % (tuple(points[0]), tuple(points[1])))
- elif isinstance(self.target, Text):
- if checkXLabel(self.target):
- axes = checkXLabel(self.target)
- axes.xaxis.labelpad = -(points[0][1] - self.target.pad_offset) / self.label_factor
- change_tracker.addChange(axes,
- ".xaxis.labelpad = %f" % axes.xaxis.labelpad)
-
- self.target.set_position(points[0])
- self.label_y = points[0][1]
- elif checkYLabel(self.target):
- axes = checkYLabel(self.target)
- axes.yaxis.labelpad = -(points[0][0] - self.target.pad_offset) / self.label_factor
- change_tracker.addChange(axes,
- ".yaxis.labelpad = %f" % axes.yaxis.labelpad)
-
- self.target.set_position(points[0])
- self.label_x = points[0][0]
- else:
- self.target.set_position(points[0])
- if isinstance(self.target, Text):
- change_tracker.addNewTextChange(self.target)
- else:
- change_tracker.addChange(self.target,
- ".set_position([%f, %f])" % self.target.get_position())
- if getattr(self.target, "xy", None) is not None:
- self.target.xy = points[1]
- change_tracker.addChange(self.target, ".xy = (%f, %f)" % tuple(self.target.xy))
- elif isinstance(self.target, Legend):
- if isinstance(self.target.axes, Axes):
- transform = self.target.axes.transAxes
- elif isinstance(self.target.figure, Figure):
- transform = self.target.figure.transFigure
- else:
- transform = self.target.figure.transSubfigure
- point = transform.inverted().transform(self.transform_inverted_points(points)[0])
- self.target._loc = tuple(point)
- change_tracker.addNewLegendChange(self.target)
- #change_tracker.addChange(self.target, "._set_loc((%f, %f))" % tuple(point))
- elif isinstance(self.target, Axes):
- position = np.array([points[0], points[1] - points[0]]).flatten()
- if self.fixed_aspect:
- position[3] = position[2] * self.target.get_position().height / self.target.get_position().width
- self.target.set_position(position)
- change_tracker.addNewAxesChange(self.target)
- #change_tracker.addChange(self.target, ".set_position([%f, %f, %f, %f])" % tuple(
- # np.array([points[0], points[1] - points[0]]).flatten()))
- setattr(self.target, "_pylustrator_cached_get_extend", None)
-
- def get_extent(self):
- # get get_extent as it can be called very frequently when checking snap conditions
- if getattr(self.target, "_pylustrator_cached_get_extend_added", False):
- setattr(self.target, "_pylustrator_cached_get_extend_added", True)
- if getattr(self.target, "_pylustrator_cached_get_extend", None) is None:
- setattr(self.target, "_pylustrator_cached_get_extend", self.do_get_extent())
- return getattr(self.target, "_pylustrator_cached_get_extend")
-
- def do_get_extent(self) -> (int, int, int, int):
- """ get the extent of the target """
- points = np.array(self.get_positions())
- return [np.min(points[:, 0]),
- np.min(points[:, 1]),
- np.max(points[:, 0]),
- np.max(points[:, 1])]
-
- def transform_points(self, points: (int, int)) -> (int, int):
- """ transform points from the targets local coordinate system to the figure coordinate system """
- transform = self.get_transform()
- return [transform.transform(p) for p in points]
-
- def transform_inverted_points(self, points: (int, int)) -> (int, int):
- """ transform points from the figure coordinate system to the targets local coordinate system """
- transform = self.get_transform()
- return [transform.inverted().transform(p) for p in points]
-
-
-class SnapBase():
- """ The base class to implement snaps. """
- data = None
-
- def __init__(self, ax_source: Artist, ax_target: Artist, edge: int):
- # wrap both object with a TargetWrapper
- self.ax_source = TargetWrapper(ax_source)
- self.ax_target = TargetWrapper(ax_target)
- self.edge = edge
- # initialize a line object for the visualisation of the snap
- self.draw_path = QtWidgets.QGraphicsPathItem()
- parent = main_figure(ax_source)._pyl_graphics_scene_snapparent
- parent.scene().addItem(self.draw_path)
- pen1 = QtGui.QPen(QtGui.QColor("red"), 2)
- pen1.setStyle(QtCore.Qt.DashLine)
- self.draw_path.setPen(pen1)
-
- def getPosition(self, target: TargetWrapper):
- """ get the position of a target """
- try:
- return target.get_extent()
- except AttributeError:
- return np.array(target.figure.transFigure.transform(target.get_position())).flatten()
-
- def getDistance(self, index: int) -> (int, int):
- """ Calculate the distance of the snap to its target """
- return 0, 0
-
- def checkSnap(self, index: int) -> Optional[float]:
- """ Return the distance to the targets or None """
- distance = self.getDistance(index)
- if abs(distance) < 10:
- return distance
- return None
-
- def checkSnapActive(self):
- """ Test if the snap condition is fullfilled """
- distance = min([self.getDistance(index) for index in [0, 1]])
- # show the snap if the distance to a target is smaller than 1
- if abs(distance) < 1:
- self.show()
- else:
- self.hide()
-
- def show(self):
- """ Implements a visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
- pass
-
- def set_data(self, xdata, ydata):
- painter_path = QtGui.QPainterPath()
- move = True
- current_pos = (0, 0)
- for x, y in zip(xdata, ydata):
- if np.isnan(x):
- move = True
- continue
- y = self.ax_target.figure.canvas.height() - y
- if move is True:
- painter_path.moveTo(x, y)
- current_pos = (x, y)
- move = False
- else:
- if current_pos[0] > x:
- painter_path.moveTo(x, y)
- painter_path.lineTo(*current_pos)
- current_pos = (x, y)
- else:
- painter_path.lineTo(x, y)
- current_pos = (x, y)
- self.draw_path.setPath(painter_path)
- self.data = (xdata, ydata)
-
- def hide(self):
- """ Hides the visualisation """
- self.set_data((), ())
-
- def remove(self):
- """ Remove the snap and its visualisation """
- self.hide()
- try:
- self.draw_path.scene().removeItem(self.draw_path)
- except ValueError:
- pass
-
-
-class SnapSameEdge(SnapBase):
- """ a snap that checks if two objects share an edge """
-
- def getDistance(self, index: int) -> (int, int):
- """ Calculate the distance of the snap to its target """
- # only if the right edge index (x or y) is queried, if not the distance is infinite
- if self.edge % 2 != index:
- return np.inf
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- # and return the difference in the target dimension
- return p1[self.edge] - p2[self.edge]
-
- def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- # if the focus edge is x, draw a line along the edge
- if self.edge % 2 == 0:
- self.set_data((p1[self.edge], p1[self.edge], p2[self.edge], p2[self.edge]),
- (p1[self.edge - 1], p1[self.edge + 1], p2[self.edge - 1], p2[self.edge + 1]))
- # if the focus edge is y
- else:
- self.set_data((p1[self.edge - 1], p1[self.edge - 3], p2[self.edge - 1], p2[self.edge - 3]),
- (p1[self.edge], p1[self.edge], p2[self.edge], p2[self.edge]))
-
-
-class SnapSameDimension(SnapBase):
- """ a snap that checks if two objects have the same width or height """
-
- def getDistance(self, index: int) -> (int, int):
- """ Calculate the distance of the snap to its target """
- # only if the right edge index (x or y) is queried, if not the distance is infinite
- if self.edge % 2 != index:
- return np.inf
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- # and the difference of the widths (or heights) of the objects
- return (p2[self.edge - 2] - p2[self.edge]) - (p1[self.edge - 2] - p1[self.edge])
-
- def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- # if the focus edge is x, draw a line though the center of each object
- if self.edge % 2 == 0:
- self.set_data((p1[0], p1[2], np.nan, p2[0], p2[2]),
- (p1[1] * 0.5 + p1[3] * 0.5, p1[1] * 0.5 + p1[3] * 0.5, np.nan, p2[1] * 0.5 + p2[3] * 0.5,
- p2[1] * 0.5 + p2[3] * 0.5))
- # if the focus edge is y
- else:
- self.set_data((p1[0] * 0.5 + p1[2] * 0.5, p1[0] * 0.5 + p1[2] * 0.5, np.nan, p2[0] * 0.5 + p2[2] * 0.5,
- p2[0] * 0.5 + p2[2] * 0.5),
- (p1[1], p1[3], np.nan, p2[1], p2[3]))
-
-
-class SnapSamePos(SnapBase):
- """ a snap that checks if two objects have the same position """
-
- def getPosition(self, text: TargetWrapper) -> (int, int):
- # get the position of an object
- return np.array(text.get_transform().transform(text.target.get_position()))
-
- def getDistance(self, index: int) -> int:
- """ Calculate the distance of the snap to its target """
- # only if the right edge index (x or y) is queried, if not the distance is infinite
- if self.edge % 2 != index:
- return np.inf
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- # get the distance of the two objects in the target dimension
- return p1[self.edge] - p2[self.edge]
-
- def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- # draw a line connecting the centers of the objects
- self.set_data((p1[0], p2[0]), (p1[1], p2[1]))
-
-
-class SnapSameBorder(SnapBase):
- """ A snap that checks if tree axes share the space between them """
-
- def __init__(self, ax_source: Artist, ax_target: Artist, ax_target2: Artist, edge: int):
- super().__init__(ax_source, ax_target, edge)
- self.ax_target2 = ax_target2
-
- def overlap(self, p1: list, p2: list, dir: int):
- """ Test if two objects have an overlapping x or y region """
- if p1[dir + 2] < p2[dir] or p1[dir] > p2[dir + 2]:
- return False
- return True
-
- def getBorders(self, p1: list, p2: list):
- borders = []
- for edge in [0, 1]:
- if self.overlap(p1, p2, 1 - edge):
- if p1[edge + 2] < p2[edge]:
- dist = p2[edge] - p1[edge + 2]
- borders.append([edge * 2 + 0, dist])
- if p1[edge] > p2[edge + 2]:
- dist = p1[edge] - p2[edge + 2]
- borders.append([edge * 2 + 1, dist])
- return np.array(borders)
-
- def getDistance(self, index: int):
- """ Calculate the distance of the snap to its target """
- # get the positions of all three targets
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- p3 = self.getPosition(self.ax_target2)
-
- for edge in [index]:
- if not (self.edge & DIR_X1) and not (self.edge & DIR_Y1):
- if p1[edge + 2] < p2[edge]:
- continue
- if not (self.edge & DIR_X0) and not (self.edge & DIR_Y0):
- if p1[edge] > p2[edge + 2]:
- continue
- if (p1[edge + 2] < p2[edge] or p1[edge] > p2[edge + 2]) and self.overlap(p1, p2, 1 - edge):
- distances = np.array([p2[edge] - p1[edge + 2], p1[edge] - p2[edge + 2]])
- index1 = np.argmax(distances)
- distance = distances[index1]
- borders = self.getBorders(p2, p3)
- if len(borders):
- deltas = distance - borders[:, 1]
- index2 = np.argmin(np.abs(deltas))
- self.dir2 = borders[index2, 0]
- self.dir1 = edge * 2 + index1
- return deltas[index2] * (-1 + 2 * index1)
- return np.inf
-
- def getConnection(self, p1: list, p2: list, dir: int):
- """ return the coordinates of a line that spans the space between to axes """
- # check which edge (e.g. x, y) and which direction (e.g. if to change the order of p1 and p2)
- edge, order = dir // 2, dir % 2
- # optionally change p1 with p2
- if order == 1:
- p1, p2 = p2, p1
- # if edge is x
- if edge == 0:
- y = np.mean([max(p1[1], p2[1]), min(p1[3], p2[3])])
- return [[p1[2], p2[0], np.nan], [y, y, np.nan]]
- # if edge is y
- x = np.mean([max(p1[0], p2[0]), min(p1[2], p2[2])])
- return [[x, x, np.nan], [p1[3], p2[1], np.nan]]
-
- def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
- # get the positions of all three axes
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition(self.ax_target)
- p3 = self.getPosition(self.ax_target2)
- # get the
- x1, y1 = self.getConnection(p1, p2, self.dir1)
- x2, y2 = self.getConnection(p2, p3, self.dir2)
- x1.extend(x2)
- y1.extend(y2)
- self.set_data(x1, y1)
-
-
-class SnapCenterWith(SnapBase):
- """ A snap that checks if a text is centered with an axes """
-
- def getPosition(self, text: TargetWrapper) -> (int, int):
- """ get the position of the first object """
- return np.array(text.get_transform().transform(text.target.get_position()))
-
- def getPosition2(self, axes: TargetWrapper) -> int:
- """ get the position of the second object """
- pos = np.array(axes.figure.transFigure.transform(axes.target.get_position()))
- p = pos[0, :]
- p[self.edge] = np.mean(pos, axis=0)[self.edge]
- return p
-
- def getDistance(self, index: int) -> int:
- """ Calculate the distance of the snap to its target """
- # only if the right edge index (x or y) is queried, if not the distance is infinite
- if self.edge % 2 != index:
- return np.inf
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition2(self.ax_target)
- # get the distance of the two objects in the target dimension
- return p1[self.edge] - p2[self.edge]
-
- def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
- # get the position of both objects
- p1 = self.getPosition(self.ax_source)
- p2 = self.getPosition2(self.ax_target)
- # draw a line connecting the centers of the objects
- self.set_data((p1[0], p2[0]), (p1[1], p2[1]))
-
-
-def checkSnaps(snaps: List[SnapBase]) -> (int, int):
- """ get the x and y offsets the snaps suggest """
- result = [0, 0]
- # iterate over x and y
- for index in range(2):
- # find the best snap
- best = np.inf
- for snap in snaps:
- delta = snap.checkSnap(index)
- if delta is not None and abs(delta) < abs(best):
- best = delta
- # if there is a snap suggestion, store it
- if best < np.inf:
- result[index] = best
- # return the best suggestion
- return result
-
-
-def checkSnapsActive(snaps: List[SnapBase]):
- """ check if snaps are active and show them if yes """
- for snap in snaps:
- snap.checkSnapActive()
-
-
-def getSnaps(targets: List[TargetWrapper], dir: int, no_height=False) -> List[SnapBase]:
- """ get all snap objects for the target and the direction """
- snaps = []
- targets = [t.target for t in targets]
- for target in targets:
- if isinstance(target, Legend):
- continue
- if isinstance(target, Text):
- if checkXLabel(target):
- snaps.append(SnapCenterWith(target, checkXLabel(target), 0))
- elif checkYLabel(target):
- snaps.append(SnapCenterWith(target, checkYLabel(target), 1))
- for ax in target.figure.axes + [target.figure]:
- for txt in ax.texts:
- # for other texts
- if txt in targets or not txt.get_visible():
- continue
- # snap to the x and the y coordinate
- x, y = txt.get_transform().transform(txt.get_position())
- snaps.append(SnapSamePos(target, txt, 0))
- snaps.append(SnapSamePos(target, txt, 1))
- continue
- for index, axes in enumerate(target.figure.axes):
- if axes not in targets and axes.get_visible():
- # axes edged
- if dir & DIR_X0:
- snaps.append(SnapSameEdge(target, axes, 0))
- if dir & DIR_Y0:
- snaps.append(SnapSameEdge(target, axes, 1))
- if dir & DIR_X1:
- snaps.append(SnapSameEdge(target, axes, 2))
- if dir & DIR_Y1:
- snaps.append(SnapSameEdge(target, axes, 3))
-
- # snap same dimensions
- if not no_height:
- if dir & DIR_X0:
- snaps.append(SnapSameDimension(target, axes, 0))
- if dir & DIR_X1:
- snaps.append(SnapSameDimension(target, axes, 2))
- if dir & DIR_Y0:
- snaps.append(SnapSameDimension(target, axes, 1))
- if dir & DIR_Y1:
- snaps.append(SnapSameDimension(target, axes, 3))
-
- for axes2 in target.figure.axes:
- if axes2 != axes and axes2 not in targets and axes2.get_visible():
- snaps.append(SnapSameBorder(target, axes, axes2, dir))
- return snaps
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# snap.py
+
+# Copyright (c) 2016-2020, Richard Gerum
+#
+# This file is part of Pylustrator.
+#
+# Pylustrator is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pylustrator is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pylustrator. If not, see
+
+from typing import TYPE_CHECKING, List, Optional, Tuple, Any, Sequence, cast
+from packaging import version
+from numpy.typing import NDArray
+
+if TYPE_CHECKING:
+ from PyQt5 import QtCore, QtGui, QtWidgets
+else:
+ from qtpy import QtCore, QtGui, QtWidgets
+
+import matplotlib as mpl
+import numpy as np
+from matplotlib.artist import Artist
+
+try: # starting from mpl version 3.6.0
+ from matplotlib.axes import Axes
+except ImportError:
+ from matplotlib.axes._subplots import Axes # ty:ignore[unresolved-import]
+from matplotlib.legend import Legend
+from matplotlib.patches import Patch, Rectangle, Ellipse, FancyArrowPatch
+from matplotlib.text import Text
+from matplotlib.figure import Figure
+
+from matplotlib.figure import SubFigure # since matplotlib 3.4.0
+from .helper_functions import main_figure
+
+# Type alias for a 2D point - internally always a numpy array
+Point = NDArray[np.floating[Any]]
+PointList = List[Point]
+
+
+def _to_point(p: Tuple[float, float] | Sequence[float] | NDArray[Any]) -> Point:
+ """Convert any point-like input to our internal Point representation (numpy array)."""
+ return np.asarray(p, dtype=np.float64)
+
+
+def _to_tuple(p: Point) -> Tuple[float, float]:
+ """Convert internal Point to tuple for matplotlib API calls."""
+ return (float(p[0]), float(p[1]))
+
+
+DIR_X0 = 1
+DIR_Y0 = 2
+DIR_X1 = 4
+DIR_Y1 = 8
+
+
+def checkXLabel(target: Artist):
+ """checks if the target is the xlabel of an axis"""
+ for axes in target.figure.axes:
+ if axes.xaxis.get_label() == target:
+ return axes
+
+
+def checkYLabel(target: Artist):
+ """checks if the target is the ylabel of an axis"""
+ for axes in target.figure.axes:
+ if axes.yaxis.get_label() == target:
+ return axes
+
+
+def cache_property(object, name):
+ if getattr(object, f"_pylustrator_cached_{name}", False) is True:
+ return
+ setattr(object, f"_pylustrator_cached_{name}", True)
+ getter = getattr(object, f"get_{name}")
+ setter = getattr(object, f"set_{name}")
+
+ def new_getter(*args, **kwargs):
+ if getattr(object, f"_pylustrator_cache_{name}", None) is None:
+ setattr(object, f"_pylustrator_cache_{name}", getter(*args, **kwargs))
+ return getattr(object, f"_pylustrator_cache_{name}", None)
+
+ def new_setter(*args, **kwargs):
+ result = setter(*args, **kwargs)
+ setattr(object, f"_pylustrator_cache_{name}", None)
+ return result
+
+ setattr(object, f"get_{name}", new_getter)
+ setattr(object, f"set_{name}", new_setter)
+
+
+class TargetWrapper(object):
+ """a wrapper to add unified set and get position methods for any matplotlib artist"""
+
+ def __init__(self, target: Artist):
+ self.target: Artist = target
+ figure = target.figure
+ if figure is None or not isinstance(figure, Figure):
+ raise ValueError("TargetWrapper needs a figure")
+ self.figure: Figure = figure
+ self.do_scale = True
+ self.fixed_aspect = False
+ # a patch uses the data_transform
+ if isinstance(self.target, Patch):
+ self.get_transform = self.target.get_data_transform
+ # axes use the figure_transform
+ elif isinstance(self.target, Axes):
+ # and optionally have a fixed aspect ratio
+ if (
+ self.target.get_aspect() != "auto"
+ and self.target.get_adjustable() != "datalim"
+ ):
+ self.fixed_aspect = True
+ # old matplotlib version
+ if version.parse(mpl.__version__) < version.parse("3.4.0"):
+ self.get_transform = lambda: self.target.figure.transFigure
+ else:
+ self.get_transform = (
+ lambda: self.target.figure.transSubfigure
+ if isinstance(self.target.figure, SubFigure)
+ else self.target.figure.transFigure
+ )
+
+ # cache the get_position
+ cache_property(self.target, "position")
+ # texts use get_transform
+ elif isinstance(self.target, Text):
+ if getattr(self.target, "xy", None) is not None:
+ self.do_scale = True
+ else:
+ self.do_scale = False
+ if checkXLabel(self.target):
+ self.label_factor = self.figure.dpi / 72.0
+ if getattr(self.target, "pad_offset", None) is None:
+ self.target.pad_offset = (
+ self.target.get_position()[1]
+ - checkXLabel(self.target).xaxis.labelpad * self.label_factor
+ )
+ self.label_y = self.target.get_position()[1]
+ elif checkYLabel(self.target):
+ self.label_factor = self.figure.dpi / 72.0
+ if getattr(self.target, "pad_offset", None) is None:
+ self.target.pad_offset = (
+ self.target.get_position()[0]
+ - checkYLabel(self.target).yaxis.labelpad * self.label_factor
+ )
+ self.label_x = self.target.get_position()[0]
+ self.get_transform = self.target.get_transform
+ # the default is to use get_transform
+ else:
+ self.get_transform = self.target.get_transform
+ self.do_scale = False
+
+ def get_positions(
+ self, use_previous_offset: bool = False, update_offset: bool = False
+ ) -> PointList:
+ """get the current position of the target Artist"""
+ points: PointList = []
+ if isinstance(self.target, Rectangle):
+ points.append(_to_point(self.target.get_xy()))
+ p2 = (
+ self.target.get_x() + self.target.get_width(),
+ self.target.get_y() + self.target.get_height(),
+ )
+ points.append(_to_point(p2))
+ elif isinstance(self.target, Ellipse):
+ c = cast(Tuple[float, float], self.target.center)
+ w = self.target.width
+ h = self.target.height
+ points.append(_to_point((c[0] - w / 2, c[1] - h / 2)))
+ points.append(_to_point((c[0] + w / 2, c[1] + h / 2)))
+ elif isinstance(self.target, FancyArrowPatch):
+ points.append(_to_point(self.target._posA_posB[0])) # ty:ignore[unresolved-attribute]
+ points.append(_to_point(self.target._posA_posB[1])) # ty:ignore[unresolved-attribute]
+ for vertex in self.target.get_path().vertices:
+ points.append(_to_point(vertex))
+ elif isinstance(self.target, Text):
+ points.append(_to_point(self.target.get_position()))
+ if checkXLabel(self.target):
+ points[0] = _to_point((points[0][0], self.label_y))
+ elif checkYLabel(self.target):
+ points[0] = _to_point((self.label_x, points[0][1]))
+ if getattr(self.target, "xy", None) is not None:
+ points.append(_to_point(self.target.xy)) # ty:ignore[unresolved-attribute]
+ bbox = self.target.get_bbox_patch()
+ if bbox:
+ points.append(
+ _to_point(
+ bbox.get_transform().transform((bbox.get_x(), bbox.get_y()))
+ )
+ )
+ points.append(
+ _to_point(
+ bbox.get_transform().transform(
+ (
+ bbox.get_x() + bbox.get_width(),
+ bbox.get_y() + bbox.get_height(),
+ )
+ )
+ )
+ )
+ points[-2:] = self.transform_inverted_points(points[-2:])
+ if use_previous_offset is True:
+ offset = getattr(self.target, "_pylustrator_offset", _to_point((0, 0)))
+ points[2] = points[0] + offset + points[2] - points[1]
+ points[1] = points[0] + offset
+ else:
+ if (
+ getattr(self.target, "_pylustrator_offset", None) is None
+ or update_offset
+ ):
+ self.target._pylustrator_offset = points[1] - points[0] # ty:ignore[invalid-assignment]
+ elif isinstance(self.target, Axes):
+ p1, p2 = np.array(self.target.get_position())
+ points.append(_to_point(p1))
+ points.append(_to_point(p2))
+ elif isinstance(self.target, SubFigure):
+ points.append(_to_point((self.target.bbox.x0, self.target.bbox.y0)))
+ points.append(_to_point((self.target.bbox.x1, self.target.bbox.y1)))
+ elif isinstance(self.target, Legend):
+ bbox = self.target.get_frame().get_bbox()
+ if isinstance(self.target.axes, Axes):
+ transform = self.target.axes.transAxes
+ elif isinstance(self.target.figure, Figure):
+ transform = self.target.figure.transFigure
+ else:
+ transform = self.target.figure.transSubfigure
+ if isinstance(self.target._get_loc(), int):
+ # if the legend doesn't have a location yet, use the left bottom corner of the bounding box
+ self.target._set_loc(
+ tuple(transform.inverted().transform(tuple([bbox.x0, bbox.y0])))
+ )
+ points.append(_to_point(transform.transform(self.target._get_loc())))
+ # add points to span bounding box around the frame
+ points.append(_to_point((bbox.x0, bbox.y0)))
+ points.append(_to_point((bbox.x1, bbox.y1)))
+ if use_previous_offset is True:
+ offset = getattr(self.target, "_pylustrator_offset", _to_point((0, 0)))
+ points[2] = points[0] + offset + points[2] - points[1]
+ points[1] = points[0] + offset
+ else:
+ if (
+ getattr(self.target, "_pylustrator_offset", None) is None
+ or update_offset
+ ):
+ self.target._pylustrator_offset = points[1] - points[0]
+ return self.transform_points(points)
+
+ def set_positions(self, points: Sequence[Point]) -> None:
+ """set the position of the target Artist"""
+ pts = self.transform_inverted_points(points)
+
+ if self.figure.figure is not None:
+ change_tracker = self.figure.figure.change_tracker
+ else:
+ change_tracker = self.figure.change_tracker
+
+ if isinstance(self.target, Rectangle):
+ self.target.set_xy(_to_tuple(pts[0]))
+ self.target.set_width(float(pts[1][0] - pts[0][0]))
+ self.target.set_height(float(pts[1][1] - pts[0][1]))
+ label = self.target.get_label()
+ if not isinstance(label, str):
+ raise TypeError("Label is not a string")
+ if label is None or not label.startswith("_rect"):
+ change_tracker.addChange(
+ self.target, ".set_xy([%f, %f])" % tuple(self.target.get_xy())
+ )
+ change_tracker.addChange(
+ self.target, ".set_width(%f)" % self.target.get_width()
+ )
+ change_tracker.addChange(
+ self.target, ".set_height(%f)" % self.target.get_height()
+ )
+ elif isinstance(self.target, Ellipse):
+ self.target.center = _to_tuple(np.mean(pts, axis=0))
+ self.target.width = float(pts[1][0] - pts[0][0])
+ self.target.height = float(pts[1][1] - pts[0][1])
+ change_tracker.addChange(
+ self.target,
+ ".center = (%f, %f)" % self.target.center,
+ )
+ change_tracker.addChange(self.target, ".width = %f" % self.target.width)
+ change_tracker.addChange(self.target, ".height = %f" % self.target.height)
+ elif isinstance(self.target, FancyArrowPatch):
+ self.target.set_positions(_to_tuple(pts[0]), _to_tuple(pts[1]))
+ change_tracker.addChange(
+ self.target,
+ ".set_positions(%s, %s)" % (_to_tuple(pts[0]), _to_tuple(pts[1])),
+ )
+ elif isinstance(self.target, Text):
+ if checkXLabel(self.target):
+ axes = checkXLabel(self.target)
+ axes.xaxis.labelpad = (
+ -(pts[0][1] - self.target.pad_offset) / self.label_factor # ty:ignore[unresolved-attribute]
+ )
+ change_tracker.addChange(
+ axes, ".xaxis.labelpad = %f" % axes.xaxis.labelpad
+ )
+
+ self.target.set_position(_to_tuple(pts[0]))
+ self.label_y = float(pts[0][1])
+ elif checkYLabel(self.target):
+ axes = checkYLabel(self.target)
+ axes.yaxis.labelpad = (
+ -(pts[0][0] - self.target.pad_offset) / self.label_factor # ty:ignore[unresolved-attribute]
+ )
+ change_tracker.addChange(
+ axes, ".yaxis.labelpad = %f" % axes.yaxis.labelpad
+ )
+
+ self.target.set_position(_to_tuple(pts[0]))
+ self.label_x = float(pts[0][0])
+ else:
+ self.target.set_position(_to_tuple(pts[0]))
+ if isinstance(self.target, Text):
+ change_tracker.addNewTextChange(self.target)
+ else:
+ change_tracker.addChange(
+ self.target,
+ ".set_position([%f, %f])" % self.target.get_position(),
+ )
+ if getattr(self.target, "xy", None) is not None:
+ self.target.xy = _to_tuple(pts[1]) # ty:ignore[invalid-assignment]
+ change_tracker.addChange(
+ self.target,
+ ".xy = (%f, %f)" % self.target.xy, # ty:ignore[unresolved-attribute]
+ )
+ elif isinstance(self.target, Legend):
+ if isinstance(self.target.axes, Axes):
+ transform = self.target.axes.transAxes
+ elif isinstance(self.target.figure, Figure):
+ transform = self.target.figure.transFigure
+ else:
+ transform = self.target.figure.transSubfigure
+ point = transform.inverted().transform(pts[0])
+ self.target._loc = tuple(point) # ty:ignore[invalid-assignment]
+ change_tracker.addNewLegendChange(self.target)
+ # change_tracker.addChange(self.target, "._set_loc((%f, %f))" % tuple(point))
+ elif isinstance(self.target, Axes):
+ position = np.array([pts[0], pts[1] - pts[0]]).flatten()
+ if self.fixed_aspect:
+ position[3] = (
+ position[2]
+ * self.target.get_position().height
+ / self.target.get_position().width
+ )
+ self.target.set_position(position)
+ change_tracker.addNewAxesChange(self.target)
+ # change_tracker.addChange(self.target, ".set_position([%f, %f, %f, %f])" % tuple(
+ # np.array([pts[0], pts[1] - pts[0]]).flatten()))
+ setattr(self.target, "_pylustrator_cached_get_extend", None)
+
+ def get_extent(self) -> Tuple[float, float, float, float]:
+ # get get_extent as it can be called very frequently when checking snap conditions
+ if getattr(self.target, "_pylustrator_cached_get_extend_added", False):
+ setattr(self.target, "_pylustrator_cached_get_extend_added", True)
+ if getattr(self.target, "_pylustrator_cached_get_extend", None) is None:
+ setattr(self.target, "_pylustrator_cached_get_extend", self.do_get_extent())
+ return getattr(self.target, "_pylustrator_cached_get_extend")
+
+ def do_get_extent(self) -> Tuple[float, float, float, float]:
+ """get the extent of the target"""
+ points = np.array(self.get_positions())
+ return (
+ np.min(points[:, 0]),
+ np.min(points[:, 1]),
+ np.max(points[:, 0]),
+ np.max(points[:, 1]),
+ )
+
+ def transform_points(self, points: Sequence[Point]) -> PointList:
+ """transform points from the targets local coordinate system to the figure coordinate system"""
+ transform = self.get_transform()
+ return [_to_point(transform.transform(p)) for p in points]
+
+ def transform_inverted_points(self, points: Sequence[Point]) -> PointList:
+ """transform points from the figure coordinate system to the targets local coordinate system"""
+ transform = self.get_transform()
+ return [_to_point(transform.inverted().transform(p)) for p in points]
+
+
+class SnapBase:
+ """The base class to implement snaps."""
+
+ data = None
+
+ def __init__(self, ax_source: Artist, ax_target: Artist, edge: int):
+ # wrap both object with a TargetWrapper
+ self.ax_source = TargetWrapper(ax_source)
+ self.ax_target = TargetWrapper(ax_target)
+ self.edge = edge
+ # initialize a line object for the visualisation of the snap
+ self.draw_path = QtWidgets.QGraphicsPathItem()
+ parent = main_figure(ax_source)._pyl_graphics_scene_snapparent
+ parent.scene().addItem(self.draw_path)
+ pen1 = QtGui.QPen(QtGui.QColor("red"), 2)
+ pen1.setStyle(QtCore.Qt.PenStyle.DashLine)
+ self.draw_path.setPen(pen1)
+
+ def getPosition(self, target: TargetWrapper) -> Tuple[float, float, float, float]:
+ """get the position of a target"""
+ try:
+ return target.get_extent()
+ except AttributeError:
+ pos = target.figure.transFigure.transform(
+ cast(Any, target.target).get_position()
+ )
+ x, y = float(pos[0]), float(pos[1])
+ return (x, y, x, y)
+
+ def getDistance(self, index: int) -> float:
+ """Calculate the distance of the snap to its target"""
+ return 0.0
+
+ def checkSnap(self, index: int) -> Optional[float]:
+ """Return the distance to the targets or None"""
+ distance = self.getDistance(index)
+ if abs(distance) < 10:
+ return distance
+ return None
+
+ def checkSnapActive(self):
+ """Test if the snap condition is fullfilled"""
+ distance = min([self.getDistance(index) for index in [0, 1]])
+ # show the snap if the distance to a target is smaller than 1
+ if abs(distance) < 1:
+ self.show()
+ else:
+ self.hide()
+
+ def show(self):
+ """Implements a visualisation of the snap, e.g. lines to indicate what objects are snapped to what"""
+ pass
+
+ def set_data(self, xdata, ydata):
+ painter_path = QtGui.QPainterPath()
+ move = True
+ current_pos = (0, 0)
+ for x, y in zip(xdata, ydata):
+ if np.isnan(x):
+ move = True
+ continue
+ y = self.ax_target.figure.canvas.height() - y
+ if move is True:
+ painter_path.moveTo(x, y)
+ current_pos = (x, y)
+ move = False
+ else:
+ if current_pos[0] > x:
+ painter_path.moveTo(x, y)
+ painter_path.lineTo(*current_pos)
+ current_pos = (x, y)
+ else:
+ painter_path.lineTo(x, y)
+ current_pos = (x, y)
+ self.draw_path.setPath(painter_path)
+ self.data = (xdata, ydata)
+
+ def hide(self):
+ """Hides the visualisation"""
+ self.set_data((), ())
+
+ def remove(self):
+ """Remove the snap and its visualisation"""
+ self.hide()
+
+ scene = self.draw_path.scene()
+ if scene is None:
+ return
+ scene.removeItem(self.draw_path)
+
+
+class SnapSameEdge(SnapBase):
+ """a snap that checks if two objects share an edge"""
+
+ def getDistance(self, index: int) -> float:
+ """Calculate the distance of the snap to its target"""
+ # only if the right edge index (x or y) is queried, if not the distance is infinite
+ if self.edge % 2 != index:
+ return np.inf
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ # and return the difference in the target dimension
+ return float(p1[self.edge] - p2[self.edge])
+
+ def show(self):
+ """A visualisation of the snap, e.g. lines to indicate what objects are snapped to what"""
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ # if the focus edge is x, draw a line along the edge
+ if self.edge % 2 == 0:
+ self.set_data(
+ (p1[self.edge], p1[self.edge], p2[self.edge], p2[self.edge]),
+ (
+ p1[self.edge - 1],
+ p1[self.edge + 1],
+ p2[self.edge - 1],
+ p2[self.edge + 1],
+ ),
+ )
+ # if the focus edge is y
+ else:
+ self.set_data(
+ (
+ p1[self.edge - 1],
+ p1[self.edge - 3],
+ p2[self.edge - 1],
+ p2[self.edge - 3],
+ ),
+ (p1[self.edge], p1[self.edge], p2[self.edge], p2[self.edge]),
+ )
+
+
+class SnapSameDimension(SnapBase):
+ """a snap that checks if two objects have the same width or height"""
+
+ def getDistance(self, index: int) -> float:
+ """Calculate the distance of the snap to its target"""
+ # only if the right edge index (x or y) is queried, if not the distance is infinite
+ if self.edge % 2 != index:
+ return np.inf
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ # and the difference of the widths (or heights) of the objects
+ return float(
+ (p2[self.edge - 2] - p2[self.edge]) - (p1[self.edge - 2] - p1[self.edge])
+ )
+
+ def show(self):
+ """A visualisation of the snap, e.g. lines to indicate what objects are snapped to what"""
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ # if the focus edge is x, draw a line though the center of each object
+ if self.edge % 2 == 0:
+ self.set_data(
+ (p1[0], p1[2], np.nan, p2[0], p2[2]),
+ (
+ p1[1] * 0.5 + p1[3] * 0.5,
+ p1[1] * 0.5 + p1[3] * 0.5,
+ np.nan,
+ p2[1] * 0.5 + p2[3] * 0.5,
+ p2[1] * 0.5 + p2[3] * 0.5,
+ ),
+ )
+ # if the focus edge is y
+ else:
+ self.set_data(
+ (
+ p1[0] * 0.5 + p1[2] * 0.5,
+ p1[0] * 0.5 + p1[2] * 0.5,
+ np.nan,
+ p2[0] * 0.5 + p2[2] * 0.5,
+ p2[0] * 0.5 + p2[2] * 0.5,
+ ),
+ (p1[1], p1[3], np.nan, p2[1], p2[3]),
+ )
+
+
+class SnapSamePos(SnapBase):
+ """a snap that checks if two objects have the same position"""
+
+ def getPosition(self, target: TargetWrapper) -> Tuple[float, float, float, float]:
+ # get the position of an object
+ if not isinstance(target.target, Text):
+ raise ValueError("SnapSamePos can only be used with text")
+ pos = target.get_transform().transform(target.target.get_position())
+ x, y = float(pos[0]), float(pos[1])
+ return (x, y, x, y)
+
+ def getDistance(self, index: int) -> float:
+ """Calculate the distance of the snap to its target"""
+ # only if the right edge index (x or y) is queried, if not the distance is infinite
+ if self.edge % 2 != index:
+ return np.inf
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ # get the distance of the two objects in the target dimension
+ return float(p1[self.edge] - p2[self.edge])
+
+ def show(self):
+ """A visualization of the snap, e.g. lines to indicate what objects are snapped to what"""
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ # draw a line connecting the centers of the objects
+ self.set_data((p1[0], p2[0]), (p1[1], p2[1]))
+
+
+class SnapSameBorder(SnapBase):
+ """A snap that checks if tree axes share the space between them"""
+
+ def __init__(
+ self, ax_source: Artist, ax_target: Artist, ax_target2: Artist, edge: int
+ ):
+ super().__init__(ax_source, ax_target, edge)
+ self.ax_target2 = TargetWrapper(ax_target2)
+
+ def overlap(
+ self,
+ p1: Tuple[float, float, float, float],
+ p2: Tuple[float, float, float, float],
+ dir: int,
+ ):
+ """Test if two objects have an overlapping x or y region"""
+ if p1[dir + 2] < p2[dir] or p1[dir] > p2[dir + 2]:
+ return False
+ return True
+
+ def getBorders(
+ self,
+ p1: Tuple[float, float, float, float],
+ p2: Tuple[float, float, float, float],
+ ):
+ borders = []
+ for edge in [0, 1]:
+ if self.overlap(p1, p2, 1 - edge):
+ if p1[edge + 2] < p2[edge]:
+ dist = p2[edge] - p1[edge + 2]
+ borders.append([edge * 2 + 0, dist])
+ if p1[edge] > p2[edge + 2]:
+ dist = p1[edge] - p2[edge + 2]
+ borders.append([edge * 2 + 1, dist])
+ return np.array(borders)
+
+ def getDistance(self, index: int):
+ """Calculate the distance of the snap to its target"""
+ # get the positions of all three targets
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ p3 = self.getPosition(self.ax_target2)
+
+ for edge in [index]:
+ if not (self.edge & DIR_X1) and not (self.edge & DIR_Y1):
+ if p1[edge + 2] < p2[edge]:
+ continue
+ if not (self.edge & DIR_X0) and not (self.edge & DIR_Y0):
+ if p1[edge] > p2[edge + 2]:
+ continue
+ if (p1[edge + 2] < p2[edge] or p1[edge] > p2[edge + 2]) and self.overlap(
+ p1,
+ p2,
+ 1 - edge,
+ ):
+ distances = np.array([p2[edge] - p1[edge + 2], p1[edge] - p2[edge + 2]])
+ index1 = np.argmax(distances)
+ distance = distances[index1]
+ borders = self.getBorders(p2, p3)
+ if len(borders):
+ deltas = distance - borders[:, 1]
+ index2 = np.argmin(np.abs(deltas))
+ self.dir2 = borders[index2, 0]
+ self.dir1 = edge * 2 + index1
+ return deltas[index2] * (-1 + 2 * index1)
+ return np.inf
+
+ def getConnection(self, p1: Tuple[float, float, float, float],
+ p2: Tuple[float, float, float, float], dir: int):
+ """return the coordinates of a line that spans the space between to axes"""
+ # check which edge (e.g. x, y) and which direction (e.g. if to change the order of p1 and p2)
+ edge, order = dir // 2, dir % 2
+ # optionally change p1 with p2
+ if order == 1:
+ p1, p2 = p2, p1
+ # if edge is x
+ if edge == 0:
+ y = np.mean([max(p1[1], p2[1]), min(p1[3], p2[3])])
+ return [[p1[2], p2[0], np.nan], [y, y, np.nan]]
+ # if edge is y
+ x = np.mean([max(p1[0], p2[0]), min(p1[2], p2[2])])
+ return [[x, x, np.nan], [p1[3], p2[1], np.nan]]
+
+ def show(self):
+ """A visualisation of the snap, e.g. lines to indicate what objects are snapped to what"""
+ # get the positions of all three axes
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition(self.ax_target)
+ p3 = self.getPosition(self.ax_target2)
+ # get the
+ x1, y1 = self.getConnection(p1, p2, self.dir1)
+ x2, y2 = self.getConnection(p2, p3, self.dir2)
+ x1.extend(x2)
+ y1.extend(y2)
+ self.set_data(x1, y1)
+
+
+class SnapCenterWith(SnapBase):
+ """A snap that checks if a text is centered with an axes"""
+
+ def getPosition(self, target: TargetWrapper) -> Tuple[float, float, float, float]:
+ """get the position of the first object"""
+ target_text = target.target
+ if not isinstance(target_text, Text):
+ raise ValueError("SnapCenterWith can only be used with axes")
+ return np.array(target.get_transform().transform(target_text.get_position()))
+
+ def getPosition2(self, axes: TargetWrapper) -> np.ndarray:
+ """get the position of the second object"""
+ target_axes = axes.target
+ if not isinstance(target_axes, Axes):
+ raise ValueError("SnapCenterWith can only be used with axes")
+ pos = np.array(axes.figure.transFigure.transform(target_axes.get_position()))
+ p = pos[0, :]
+ p[self.edge] = np.mean(pos, axis=0)[self.edge]
+ return p
+
+ def getDistance(self, index: int) -> float:
+ """Calculate the distance of the snap to its target"""
+ # only if the right edge index (x or y) is queried, if not the distance is infinite
+ if self.edge % 2 != index:
+ return np.inf
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition2(self.ax_target)
+ # get the distance of the two objects in the target dimension
+ return float(p1[self.edge] - p2[self.edge])
+
+ def show(self):
+ """A visualisation of the snap, e.g. lines to indicate what objects are snapped to what"""
+ # get the position of both objects
+ p1 = self.getPosition(self.ax_source)
+ p2 = self.getPosition2(self.ax_target)
+ # draw a line connecting the centers of the objects
+ self.set_data((p1[0], p2[0]), (p1[1], p2[1]))
+
+
+def checkSnaps(snaps: List[SnapBase]) -> list[float]:
+ """get the x and y offsets the snaps suggest"""
+ result: list[float] = [0, 0]
+ # iterate over x and y
+ for index in range(2):
+ # find the best snap
+ best = np.inf
+ for snap in snaps:
+ delta = snap.checkSnap(index)
+ if delta is not None and abs(delta) < abs(best):
+ best = delta
+ # if there is a snap suggestion, store it
+ if best < np.inf:
+ result[index] = best
+ # return the best suggestion
+ return result
+
+
+def checkSnapsActive(snaps: List[SnapBase]):
+ """check if snaps are active and show them if yes"""
+ for snap in snaps:
+ snap.checkSnapActive()
+
+
+def getSnaps(targets: List[TargetWrapper], dir: int, no_height=False) -> List[SnapBase]:
+ """get all snap objects for the target and the direction"""
+ snaps = []
+ target_artists: List[Artist] = [t.target for t in targets]
+ for target in target_artists:
+ if isinstance(target, Legend):
+ continue
+ if isinstance(target, Text):
+ if checkXLabel(target):
+ snaps.append(SnapCenterWith(target, checkXLabel(target), 0))
+ elif checkYLabel(target):
+ snaps.append(SnapCenterWith(target, checkYLabel(target), 1))
+ for ax in target.figure.axes + [target.figure]:
+ for txt in ax.texts:
+ # for other texts
+ if txt in target_artists or not txt.get_visible():
+ continue
+ # snap to the x and the y coordinate
+ x, y = txt.get_transform().transform(txt.get_position())
+ snaps.append(SnapSamePos(target, txt, 0))
+ snaps.append(SnapSamePos(target, txt, 1))
+ continue
+ for index, axes in enumerate(target.figure.axes):
+ if axes not in target_artists and axes.get_visible():
+ # axes edged
+ if dir & DIR_X0:
+ snaps.append(SnapSameEdge(target, axes, 0))
+ if dir & DIR_Y0:
+ snaps.append(SnapSameEdge(target, axes, 1))
+ if dir & DIR_X1:
+ snaps.append(SnapSameEdge(target, axes, 2))
+ if dir & DIR_Y1:
+ snaps.append(SnapSameEdge(target, axes, 3))
+
+ # snap same dimensions
+ if not no_height:
+ if dir & DIR_X0:
+ snaps.append(SnapSameDimension(target, axes, 0))
+ if dir & DIR_X1:
+ snaps.append(SnapSameDimension(target, axes, 2))
+ if dir & DIR_Y0:
+ snaps.append(SnapSameDimension(target, axes, 1))
+ if dir & DIR_Y1:
+ snaps.append(SnapSameDimension(target, axes, 3))
+
+ for axes2 in target.figure.axes:
+ if (
+ axes2 != axes
+ and axes2 not in target_artists
+ and axes2.get_visible()
+ ):
+ snaps.append(SnapSameBorder(target, axes, axes2, dir))
+ return snaps
diff --git a/pyproject.toml b/pyproject.toml
index a1ed5e5..a2c735a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,25 +1,47 @@
-[tool.poetry]
+[project]
name = "pylustrator"
version = "1.3.0"
description = "Adds interactivity to arrange panels in matplotlib."
-authors = ["rgerum <14153051+rgerum@users.noreply.github.com>"]
-license = "GPLv3"
+authors = [{ name = "Richard Gerum", email = "pypi.org.zookeeper162@passmail.net" }]
+license = "GPL-3.0-or-later"
readme = "README.md"
-repository = 'https://github.com/rgerum/pylustrator'
+requires-python = ">=3.9"
+dependencies = [
+ "natsort>=2.0.0",
+ "numpy>=1.0.3",
+ "matplotlib>=2.0.2",
+ "PyQt5>=5.6",
+ "qtawesome>=0.5.0",
+ "scikit-image>=0.7.0",
+ "qtpy>=2.4.3",
+]
-[tool.poetry.dependencies]
-python = ">=3.9"
-natsort = ">=2.0.0"
-numpy = ">=1.0.3"
-matplotlib = ">=2.0.2"
-PyQt5 = ">=5.6"
-qtawesome = ">=0.5.0"
-scikit-image = ">=0.7.0"
+[project.urls]
+Homepage = "https://github.com/rgerum/pylustrator"
+Repository = "https://github.com/rgerum/pylustrator"
+Issues = "https://github.com/rgerum/pylustrator/issues"
+Documentation = "https://pylustrator.readthedocs.io/en/latest/"
-[tool.poetry.group.test.dependencies]
-pytest = "^7.2.0"
+[project.optional-dependencies]
+dev = ["ruff>=0.14.11", "ty>=0.0.10"]
+test = ["pytest>=7.2.0,<8.0.0"]
+[tool.ruff]
+exclude = ["docs/example_pylustrator.py"]
[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+requires = ["setuptools>=42", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[dependency-groups]
+docs = [
+ "mock>=5.2.0",
+ "nbsphinx>=0.9.8",
+ "sphinx-rtd-theme>=3.0.2",
+ "sphinxcontrib-bibtex>=2.6.5",
+]
+
+[tool.setuptools]
+packages = ["pylustrator", "pylustrator.components"]
+include-package-data = true
+
diff --git a/setup.py b/setup.py
deleted file mode 100644
index a6abdbd..0000000
--- a/setup.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# setup.py
-
-# Copyright (c) 2016-2020, Richard Gerum
-#
-# This file is part of Pylustrator.
-#
-# Pylustrator is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pylustrator is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pylustrator. If not, see
-
-from setuptools import setup
-
-# read the contents of your README file
-from pathlib import Path
-this_directory = Path(__file__).parent
-long_description = (this_directory / "README.md").read_text()
-
-setup(name='pylustrator',
- version="1.3.0",
- description='Adds interactivity to arrange panels in matplotlib',
- long_description=long_description,
- url='https://github.com/rgerum/pylustrator',
- license="GPLv3",
- author='Richard Gerum',
- author_email='richard.gerum@fau.de',
- packages=['pylustrator', 'pylustrator.components'],
- include_package_data=True,
- install_requires=[
- 'natsort',
- 'numpy',
- 'matplotlib',
- 'qtawesome',
- 'scikit-image'
- ],
- )
diff --git a/tests/base_test_class.py b/tests/base_test_class.py
index 0c3dec5..7208c53 100644
--- a/tests/base_test_class.py
+++ b/tests/base_test_class.py
@@ -3,7 +3,7 @@
import re
from pathlib import Path
import matplotlib.pyplot as plt
-from matplotlib.backend_bases import MouseEvent, KeyEvent
+from matplotlib.backend_bases import MouseEvent
from typing import Any
@@ -11,7 +11,8 @@ def ensure_list(obj, count=1):
if isinstance(obj, list):
return obj
else:
- return [obj]*count
+ return [obj] * count
+
def select_elements(fig, get_obj_list):
if not isinstance(get_obj_list, list):
@@ -26,15 +27,19 @@ def select_elements(fig, get_obj_list):
if index == 0:
fig.figure_dragger.select_element(get_obj)
else:
- fig.figure_dragger.select_element(get_obj, MouseEvent("select", fig.canvas, 0, 0, key="shift"))
+ fig.figure_dragger.select_element(
+ get_obj, MouseEvent("select", fig.canvas, 0, 0, key="shift")
+ )
class Undefined:
pass
+
class NotInSave:
pass
+
class Value:
def __init__(self, value, value_saved=Undefined):
self.value = value
@@ -92,7 +97,7 @@ def get_script_text(self):
def run_plot_script(self):
text = self.get_script_text()
- exec(compile(text, self.filename, 'exec'), globals())
+ exec(compile(text, self.filename, "exec"), globals())
self.fig = plt.gcf()
return plt.gcf(), text
@@ -114,9 +119,13 @@ def check_line_in_file(self, line_start):
def grab_args(*args, **kwargs):
return args, kwargs
- arguments = re.match(re.escape(line_start)+r"(.*)\)", line).groups()[0]
+ arguments = re.match(
+ re.escape(line_start) + r"(.*)\)", line
+ ).groups()[0]
return line, eval(f"grab_args({arguments})")
- if line.startswith("#% start: automatic generated code from pylustrator"):
+ if line.startswith(
+ "#% start: automatic generated code from pylustrator"
+ ):
in_block = True
if line.startswith("#% end: automatic generated code from pylustrator"):
in_block = False
@@ -129,7 +138,8 @@ def move_element(self, offset, element=None):
# don't move it and save the result
fig.selection.start_move()
fig.selection.has_moved = True
- fig.selection.addOffset(offset, fig.selection.dir)
+ dir_value = fig.selection.dir if fig.selection.dir is not None else 0
+ fig.selection.addOffset(offset, dir_value)
fig.selection.end_move()
def assertEqualStringOrArray(self, first, second, msg) -> None:
@@ -138,7 +148,9 @@ def assertEqualStringOrArray(self, first, second, msg) -> None:
elif isinstance(first, list) and isinstance(first[0], str):
self.assertEqual(first, second)
else:
- np.testing.assert_allclose(np.asarray(second), np.asarray(first), rtol=1e-3, err_msg=msg)
+ np.testing.assert_allclose(
+ np.asarray(second), np.asarray(first), rtol=1e-3, err_msg=msg
+ )
def check_saved_property(self, property_name, line_command, value2, test_run=""):
# find the saved string and check the numbers
@@ -165,18 +177,41 @@ def check_saved_property(self, property_name, line_command, value2, test_run="")
kwargs["grid"] = args[0]
if property_name == "despine":
kwargs["despine"] = args[0]
- self.assertEqualStringOrArray(value2, kwargs.get(property_name),
- f"Property '{property_name}' not saved correctly. [{test_run}]")
-
- def change_property(self, property_name, value, call, get_obj, line_command, test_run, value2: Any = "undefined", get_function=None, test_saved_value=None):
+ self.assertEqualStringOrArray(
+ value2,
+ kwargs.get(property_name),
+ f"Property '{property_name}' not saved correctly. [{test_run}]",
+ )
+
+ def change_property(
+ self,
+ property_name,
+ value,
+ call,
+ get_obj,
+ line_command,
+ test_run,
+ value2: Any = "undefined",
+ get_function=None,
+ test_saved_value=None,
+ ):
if value2 == "undefined":
value2 = value
if isinstance(get_obj, list):
- return self.change_property2(property_name, value, call, get_obj, line_command, test_run,
- value2_list=value2)
+ return self.change_property2(
+ property_name,
+ value,
+ call,
+ get_obj,
+ line_command,
+ test_run,
+ value2_list=value2,
+ )
if get_function is None:
- get_function = lambda: getattr(get_obj(), f"get_{property_name}")()
+
+ def get_function():
+ return getattr(get_obj(), f"get_{property_name}")()
# get the initial code
fig = self.fig
@@ -196,24 +231,37 @@ def change_property(self, property_name, value, call, get_obj, line_command, tes
fig.change_tracker.save()
# test if the text has the right weight
- self.assertEqualStringOrArray(value, get_function(),
- f"Property '{property_name}' not set correctly. [{test_run}]")
+ self.assertEqualStringOrArray(
+ value,
+ get_function(),
+ f"Property '{property_name}' not set correctly. [{test_run}]",
+ )
# test undo and redo
fig.window.undo()
print("current_value", current_value, get_function())
- self.assertEqualStringOrArray(current_value, get_function(),
- f"Property '{property_name}' undo failed. [{test_run}]")
+ self.assertEqualStringOrArray(
+ current_value,
+ get_function(),
+ f"Property '{property_name}' undo failed. [{test_run}]",
+ )
# the output after undo should be the same as the beginning
if self.no_undo_save_test is False:
print("---- save after undo ----", end="")
fig.change_tracker.save()
- self.assertEqual(text0, self.get_script_text(), f"Saved differently after undo. Property '{property_name}'. [{test_run}]")
+ self.assertEqual(
+ text0,
+ self.get_script_text(),
+ f"Saved differently after undo. Property '{property_name}'. [{test_run}]",
+ )
fig.window.redo()
- self.assertEqualStringOrArray(value, get_function(),
- f"Property '{property_name}' redo failed. [{test_run}]")
+ self.assertEqualStringOrArray(
+ value,
+ get_function(),
+ f"Property '{property_name}' redo failed. [{test_run}]",
+ )
print("---- save after redo ----", end="")
fig.change_tracker.save()
@@ -229,8 +277,11 @@ def change_property(self, property_name, value, call, get_obj, line_command, tes
# test if the text has the right weight
try:
- self.assertEqualStringOrArray(value, get_function(),
- f"Property '{property_name}' not restored correctly. [{test_run}]")
+ self.assertEqualStringOrArray(
+ value,
+ get_function(),
+ f"Property '{property_name}' not restored correctly. [{test_run}]",
+ )
# when the task is to delete then finding it is an error
if property_name == "visible" and line_command.endswith(".text("):
raise IndexError
@@ -247,19 +298,40 @@ def change_property(self, property_name, value, call, get_obj, line_command, tes
raise err
# the output should still be the same
- self.assertEqual(text, self.get_script_text(), f"Saved differently. Property '{property_name}'. [{test_run}]")
+ self.assertEqual(
+ text,
+ self.get_script_text(),
+ f"Saved differently. Property '{property_name}'. [{test_run}]",
+ )
def check_property(self, get_obj_list, property_name_list, current_values, desc):
- for get_obj, property_name, current_value in zip(get_obj_list, property_name_list, current_values):
- self.assertEqualStringOrArray(current_value, getattr(get_obj(), f"get_{property_name}")(),
- desc)
+ for get_obj, property_name, current_value in zip(
+ get_obj_list, property_name_list, current_values
+ ):
+ self.assertEqualStringOrArray(
+ current_value, getattr(get_obj(), f"get_{property_name}")(), desc
+ )
def compare_list(self, get_obj_list, current_values, target_values, desc):
- for get_obj, target_value, current_value in zip(get_obj_list, target_values, current_values):
+ for get_obj, target_value, current_value in zip(
+ get_obj_list, target_values, current_values
+ ):
self.assertEqualStringOrArray(target_value, current_value, desc)
- def change_property2(self, property_name_list, value_list, call, get_obj_list, line_command_list,
- test_run, value2_list="undefined", show=False, get_function=None, delete=False, test_saved_value=None):
+ def change_property2(
+ self,
+ property_name_list,
+ value_list,
+ call,
+ get_obj_list,
+ line_command_list,
+ test_run,
+ value2_list="undefined",
+ show=False,
+ get_function=None,
+ delete=False,
+ test_saved_value=None,
+ ):
if value2_list == "undefined":
value2_list = value_list
get_obj_list = ensure_list(get_obj_list)
@@ -269,8 +341,12 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
line_command_list = ensure_list(line_command_list, len(get_obj_list))
if get_function is None:
- get_function = lambda: [getattr(get_obj(), f"get_{property_name}")()
- for get_obj, property_name in zip(get_obj_list, property_name_list)]
+
+ def get_function():
+ return [
+ getattr(get_obj(), f"get_{property_name}")()
+ for get_obj, property_name in zip(get_obj_list, property_name_list)
+ ]
fig = self.fig
@@ -290,24 +366,39 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
fig.change_tracker.save()
# test if the text has the right weight
- self.compare_list(get_obj_list, get_function(), value_list,
- f"Property '{property_name_list[0]}' not set correctly. [{test_run}]")
+ self.compare_list(
+ get_obj_list,
+ get_function(),
+ value_list,
+ f"Property '{property_name_list[0]}' not set correctly. [{test_run}]",
+ )
# test undo and redo
fig.window.undo()
- self.compare_list(get_obj_list, get_function(), current_values,
- f"Property '{property_name_list[0]}' undo failed. [{test_run}]")
+ self.compare_list(
+ get_obj_list,
+ get_function(),
+ current_values,
+ f"Property '{property_name_list[0]}' undo failed. [{test_run}]",
+ )
if self.no_undo_save_test is False:
# the output after undo should be the same as the beginning
print("\n---- save after undo ----", end="")
fig.change_tracker.save()
- self.assertEqual(text0, self.get_script_text(),
- f"Saved differently after undo. Property '{property_name_list[0]}'. [{test_run}]")
+ self.assertEqual(
+ text0,
+ self.get_script_text(),
+ f"Saved differently after undo. Property '{property_name_list[0]}'. [{test_run}]",
+ )
fig.window.redo()
- self.compare_list(get_obj_list, get_function(), value_list,
- f"Property '{property_name_list[0]}' redo failed. [{test_run}]")
+ self.compare_list(
+ get_obj_list,
+ get_function(),
+ value_list,
+ f"Property '{property_name_list[0]}' redo failed. [{test_run}]",
+ )
print("\n---- save after redo ----", end="")
fig.change_tracker.save()
@@ -315,7 +406,9 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
# find the saved string and check the numbers
# find the saved string and check the numbers
if test_saved_value is None:
- for command, value2, property_name in zip(line_command_list, value2_list, property_name_list):
+ for command, value2, property_name in zip(
+ line_command_list, value2_list, property_name_list
+ ):
self.check_saved_property(property_name, command, value2, test_run)
else:
test_saved_value()
@@ -325,8 +418,12 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
try:
# test if the text has the right weight
- self.compare_list(get_obj_list, get_function(), value_list,
- f"Property '{property_name_list[0]}' not set failed. [{test_run}]")
+ self.compare_list(
+ get_obj_list,
+ get_function(),
+ value_list,
+ f"Property '{property_name_list[0]}' not set failed. [{test_run}]",
+ )
# don't move it and save the result
self.move_element((0, 0), get_obj_list)
@@ -341,5 +438,8 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
fig.change_tracker.save()
# the output should still be the same
- self.assertEqual(text, self.get_script_text(),
- f"Saved differently. Property '{property_name_list}'. [{test_run}]")
\ No newline at end of file
+ self.assertEqual(
+ text,
+ self.get_script_text(),
+ f"Saved differently. Property '{property_name_list}'. [{test_run}]",
+ )
diff --git a/tests/test_axes.py b/tests/test_axes.py
index 83ac667..6b52fc7 100644
--- a/tests/test_axes.py
+++ b/tests/test_axes.py
@@ -3,7 +3,6 @@
class TestAxes(BaseTest):
-
def test_move_axes(self):
# get the figure
fig, text = self.run_plot_script()
@@ -14,15 +13,23 @@ def test_move_axes(self):
# find the saved string and check the numbers
line, (args, kwargs) = self.check_line_in_file("plt.figure(1).axes[0].set(")
- np.testing.assert_almost_equal(kwargs["position"], [0.123438, 0.11, 0.227941, 0.77], 2,
- "Figure movement not correctly written to file")
+ np.testing.assert_almost_equal(
+ kwargs["position"],
+ [0.123438, 0.11, 0.227941, 0.77],
+ 2,
+ "Figure movement not correctly written to file",
+ )
# run the file again
fig, text = self.run_plot_script()
# test if the axes is at the right position
- np.testing.assert_almost_equal(np.array(fig.axes[0].get_position()), [[0.123438, 0.11], [0.351379, 0.88]], 2,
- "Saved axes not loaded correctly")
+ np.testing.assert_almost_equal(
+ np.array(fig.axes[0].get_position()),
+ [[0.123438, 0.11], [0.351379, 0.88]],
+ 2,
+ "Saved axes not loaded correctly",
+ )
# don't move it and save the result
self.move_element((0, 0), fig.axes[0])
@@ -35,82 +42,155 @@ def test_axis_limits(self):
# get the figure
fig, text = self.run_plot_script()
- get_axes = lambda: fig.axes[0]
+ def get_axes():
+ return fig.axes[0]
+
line_command = "plt.figure(1).axes[0].set("
test_run = "Change axes limits."
- self.change_property2("xlim", (1, 10), lambda _: self.fig.window.input_properties.input_xaxis.input_lim.setValue((1, 10), signal=True), get_axes, line_command,
- test_run)
-
- self.change_property2("ylim", (2, 8), lambda _: self.fig.window.input_properties.input_yaxis.input_lim.setValue((2, 8), signal=True), get_axes, line_command,
- test_run)
-
- self.change_property2("xlabel", "label",
- lambda _: self.fig.window.input_properties.input_xaxis.input_label.setText("label",
- signal=True),
- get_axes, line_command,
- test_run)
-
- self.change_property2("ylabel", "label",
- lambda _: self.fig.window.input_properties.input_yaxis.input_label.setText("label",
- signal=True),
- get_axes, line_command,
- test_run)
-
- get_axes = [lambda: fig.axes[0], lambda: fig.axes[1]]
+ self.change_property2(
+ "xlim",
+ (1, 10),
+ lambda _: self.fig.window.input_properties.input_xaxis.input_lim.setValue(
+ (1, 10), signal=True
+ ),
+ get_axes,
+ line_command,
+ test_run,
+ )
+
+ self.change_property2(
+ "ylim",
+ (2, 8),
+ lambda _: self.fig.window.input_properties.input_yaxis.input_lim.setValue(
+ (2, 8), signal=True
+ ),
+ get_axes,
+ line_command,
+ test_run,
+ )
+
+ self.change_property2(
+ "xlabel",
+ "label",
+ lambda _: self.fig.window.input_properties.input_xaxis.input_label.setText(
+ "label", signal=True
+ ),
+ get_axes,
+ line_command,
+ test_run,
+ )
+
+ self.change_property2(
+ "ylabel",
+ "label",
+ lambda _: self.fig.window.input_properties.input_yaxis.input_label.setText(
+ "label", signal=True
+ ),
+ get_axes,
+ line_command,
+ test_run,
+ )
+
+ get_axes_list = [lambda: fig.axes[0], lambda: fig.axes[1]]
line_command = ["plt.figure(1).axes[0].set(", "plt.figure(1).axes[1].set("]
test_run = "Change axes limits of two axes."
- self.change_property2("xlim", (0.3, 10.7),
- lambda _: self.fig.window.input_properties.input_xaxis.input_lim.setValue((0.3, 10.7),
- signal=True),
- get_axes, line_command,
- test_run)
-
- self.change_property2("ylim", (0.3, 10.7),
- lambda _: self.fig.window.input_properties.input_yaxis.input_lim.setValue((0.3, 10.7),
- signal=True),
- get_axes, line_command,
- test_run)
-
- self.change_property2("xlabel", "label2",
- lambda _: self.fig.window.input_properties.input_xaxis.input_label.setText("label2",
- signal=True),
- get_axes, line_command,
- test_run)
-
- self.change_property2("ylabel", "label2",
- lambda _: self.fig.window.input_properties.input_yaxis.input_label.setText("label2",
- signal=True),
- get_axes, line_command,
- test_run)
+ self.change_property2(
+ "xlim",
+ (0.3, 10.7),
+ lambda _: self.fig.window.input_properties.input_xaxis.input_lim.setValue(
+ (0.3, 10.7), signal=True
+ ),
+ get_axes_list,
+ line_command,
+ test_run,
+ )
+
+ self.change_property2(
+ "ylim",
+ (0.3, 10.7),
+ lambda _: self.fig.window.input_properties.input_yaxis.input_lim.setValue(
+ (0.3, 10.7), signal=True
+ ),
+ get_axes_list,
+ line_command,
+ test_run,
+ )
+
+ self.change_property2(
+ "xlabel",
+ "label2",
+ lambda _: self.fig.window.input_properties.input_xaxis.input_label.setText(
+ "label2", signal=True
+ ),
+ get_axes_list,
+ line_command,
+ test_run,
+ )
+
+ self.change_property2(
+ "ylabel",
+ "label2",
+ lambda _: self.fig.window.input_properties.input_yaxis.input_label.setText(
+ "label2", signal=True
+ ),
+ get_axes_list,
+ line_command,
+ test_run,
+ )
+
def test_axis_grid(self):
# get the figure
fig, text = self.run_plot_script()
- get_axes = lambda: fig.axes[0]
+ def get_axes():
+ return fig.axes[0]
+
line_command = "plt.figure(1).axes[0].grid("
test_run = "Change axes grid."
- self.change_property("grid", True, lambda _: self.fig.window.input_properties.button_grid.clicked.emit(True), get_axes, line_command,
- test_run, get_function=lambda: getattr(get_axes(), "_gridOnMajor", False) or getattr(get_axes().xaxis, "_major_tick_kw", {"gridOn": False})['gridOn'])
+ self.change_property(
+ "grid",
+ True,
+ lambda _: self.fig.window.input_properties.button_grid.clicked.emit(True),
+ get_axes,
+ line_command,
+ test_run,
+ get_function=lambda: getattr(get_axes(), "_gridOnMajor", False)
+ or getattr(get_axes().xaxis, "_major_tick_kw", {"gridOn": False})["gridOn"],
+ )
def test_axis_despine(self):
# get the figure
fig, text = self.run_plot_script()
- get_axes = lambda: fig.axes[0]
+ def get_axes():
+ return fig.axes[0]
+
line_command = "plt.figure(1).axes[0].spines[['right', 'top']].set_visible("
test_run = "Change axes despine."
- self.change_property("despine", False, lambda _: self.fig.window.input_properties.button_despine.clicked.emit(True), get_axes, line_command,
- test_run, get_function=lambda: get_axes().spines['right'].get_visible() and get_axes().spines['top'].get_visible())
+ self.change_property(
+ "despine",
+ False,
+ lambda _: self.fig.window.input_properties.button_despine.clicked.emit(
+ True
+ ),
+ get_axes,
+ line_command,
+ test_run,
+ get_function=lambda: get_axes().spines["right"].get_visible()
+ and get_axes().spines["top"].get_visible(),
+ )
def test_axis_ticks(self):
# get the figure
fig, text = self.run_plot_script()
- get_axes = lambda: fig.axes[0]
+ def get_axes():
+ return fig.axes[0]
+
test_run = "Change axes ticks."
line_command = "plt.figure(1).axes[0].set("
@@ -118,66 +198,127 @@ def check_saved_property(xy):
def check():
# find the saved string and check the numbers
line, (args, kwargs) = self.check_line_in_file(line_command)
- self.assertEqualStringOrArray([1., 2.2, 3., 5.], kwargs[f"{xy}ticks"],
- f"Property 'ticks' not saved correctly. [{test_run}]")
- self.assertEqualStringOrArray(["1", "2.2", "3", "5"], kwargs[f"{xy}ticklabels"],
- f"Property 'ticks' not saved correctly. [{test_run}]")
+ self.assertEqualStringOrArray(
+ [1.0, 2.2, 3.0, 5.0],
+ kwargs[f"{xy}ticks"],
+ f"Property 'ticks' not saved correctly. [{test_run}]",
+ )
+ self.assertEqualStringOrArray(
+ ["1", "2.2", "3", "5"],
+ kwargs[f"{xy}ticklabels"],
+ f"Property 'ticks' not saved correctly. [{test_run}]",
+ )
+
return check
# test_saved_value
def set_ticks(_):
self.fig.window.input_properties.input_xaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_xaxis.tick_edit.input_ticks.setText("1\n2.2\n3\n5", signal=True)
-
- self.change_property("xticks", [1., 2.2, 3., 5.], set_ticks, get_axes, line_command, test_run, test_saved_value=check_saved_property("x"))
+ self.fig.window.input_properties.input_xaxis.tick_edit.input_ticks.setText(
+ "1\n2.2\n3\n5", signal=True
+ )
+
+ self.change_property(
+ "xticks",
+ [1.0, 2.2, 3.0, 5.0],
+ set_ticks,
+ get_axes,
+ line_command,
+ test_run,
+ test_saved_value=check_saved_property("x"),
+ )
def set_ticks(_):
self.fig.window.input_properties.input_yaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_yaxis.tick_edit.input_ticks.setText("1\n2.2\n3\n5", signal=True)
-
- self.change_property("yticks", [1., 2.2, 3., 5.], set_ticks, get_axes, line_command, test_run, test_saved_value=check_saved_property("y"))
+ self.fig.window.input_properties.input_yaxis.tick_edit.input_ticks.setText(
+ "1\n2.2\n3\n5", signal=True
+ )
+
+ self.change_property(
+ "yticks",
+ [1.0, 2.2, 3.0, 5.0],
+ set_ticks,
+ get_axes,
+ line_command,
+ test_run,
+ test_saved_value=check_saved_property("y"),
+ )
def check_saved_property(xy):
def check():
# find the saved string and check the numbers
line, (args, kwargs) = self.check_line_in_file(line_command)
- self.assertEqualStringOrArray([1., 2., 3., 5., 10], kwargs[f"{xy}ticks"],
- f"Property 'ticks' not saved correctly. [{test_run}]")
- self.assertEqualStringOrArray(["a", "b", "c", "5", r'$\mathdefault{10^{1}}$'], kwargs[f"{xy}ticklabels"],
- f"Property 'ticks' not saved correctly. [{test_run}]")
+ self.assertEqualStringOrArray(
+ [1.0, 2.0, 3.0, 5.0, 10],
+ kwargs[f"{xy}ticks"],
+ f"Property 'ticks' not saved correctly. [{test_run}]",
+ )
+ self.assertEqualStringOrArray(
+ ["a", "b", "c", "5", r"$\mathdefault{10^{1}}$"],
+ kwargs[f"{xy}ticklabels"],
+ f"Property 'ticks' not saved correctly. [{test_run}]",
+ )
# test_saved_value
def set_ticks(_):
self.fig.window.input_properties.input_xaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_xaxis.tick_edit.input_ticks.setText('1 "a"\n2 "b\n3 c\n5\n10^1', signal=True)
-
- self.change_property("xticks", [1., 2., 3., 5., 10], set_ticks, get_axes, line_command, test_run,
- test_saved_value=check_saved_property("x"))
+ self.fig.window.input_properties.input_xaxis.tick_edit.input_ticks.setText(
+ '1 "a"\n2 "b\n3 c\n5\n10^1', signal=True
+ )
+
+ self.change_property(
+ "xticks",
+ [1.0, 2.0, 3.0, 5.0, 10],
+ set_ticks,
+ get_axes,
+ line_command,
+ test_run,
+ test_saved_value=check_saved_property("x"),
+ )
def set_ticks(_):
self.fig.window.input_properties.input_yaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_yaxis.tick_edit.input_ticks.setText('1 "a"\n2 "b\n3 c\n5\n10^1', signal=True)
-
- self.change_property("yticks", [1., 2., 3., 5., 10], set_ticks, get_axes, line_command, test_run,
- test_saved_value=check_saved_property("y"))
+ self.fig.window.input_properties.input_yaxis.tick_edit.input_ticks.setText(
+ '1 "a"\n2 "b\n3 c\n5\n10^1', signal=True
+ )
+
+ self.change_property(
+ "yticks",
+ [1.0, 2.0, 3.0, 5.0, 10],
+ set_ticks,
+ get_axes,
+ line_command,
+ test_run,
+ test_saved_value=check_saved_property("y"),
+ )
def set_xlog(_):
self.fig.window.input_properties.input_xaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_xaxis.tick_edit.input_scale.setText("log", signal=True)
+ self.fig.window.input_properties.input_xaxis.tick_edit.input_scale.setText(
+ "log", signal=True
+ )
- self.change_property("xscale", "log", set_xlog, get_axes, line_command, test_run)
+ self.change_property(
+ "xscale", "log", set_xlog, get_axes, line_command, test_run
+ )
def set_ylog(_):
self.fig.window.input_properties.input_yaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_yaxis.tick_edit.input_scale.setText("log", signal=True)
+ self.fig.window.input_properties.input_yaxis.tick_edit.input_scale.setText(
+ "log", signal=True
+ )
- self.change_property("yscale", "log", set_ylog, get_axes, line_command, test_run)
+ self.change_property(
+ "yscale", "log", set_ylog, get_axes, line_command, test_run
+ )
def test_minor_axis_ticks(self):
# get the figure
fig, text = self.run_plot_script()
- get_axes = lambda: fig.axes[0]
+ def get_axes():
+ return fig.axes[0]
+
test_run = "Change axes ticks."
self.move_element((0, 0), fig.axes[0])
@@ -188,30 +329,58 @@ def check_saved_property(xy):
def check():
# find the saved string and check the numbers
line, (args, kwargs) = self.check_line_in_file(line_command)
- self.assertEqualStringOrArray(True, kwargs["minor"], f"Property 'ticks' not saved correctly. [{test_run}]")
- self.assertEqualStringOrArray([0.01, 0.1, 0.2, 0.3, 0.5], args[0],
- f"Property 'ticks' not saved correctly. [{test_run}]")
- self.assertEqualStringOrArray([r'$\mathdefault{10^{-2}}$', "a", "b", "c", "0.5"],
- args[1],
- f"Property 'ticks' not saved correctly. [{test_run}]")
+ self.assertEqualStringOrArray(
+ True,
+ kwargs["minor"],
+ f"Property 'ticks' not saved correctly. [{test_run}]",
+ )
+ self.assertEqualStringOrArray(
+ [0.01, 0.1, 0.2, 0.3, 0.5],
+ args[0],
+ f"Property 'ticks' not saved correctly. [{test_run}]",
+ )
+ self.assertEqualStringOrArray(
+ [r"$\mathdefault{10^{-2}}$", "a", "b", "c", "0.5"],
+ args[1],
+ f"Property 'ticks' not saved correctly. [{test_run}]",
+ )
return check
+
# minor ticks
def set_ticks(_):
self.fig.window.input_properties.input_xaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_xaxis.tick_edit.input_ticks2.setText('10^-2\n0.1 "a"\n0.2 "b\n0.3 c\n0.5',
- signal=True)
-
- self.change_property2("xticks", [0.01, 0.1, 0.2, 0.3, 0.5], set_ticks, get_axes, line_command, test_run,
- test_saved_value=check_saved_property("x"), get_function=lambda: get_axes().get_xticks(minor=True))
+ self.fig.window.input_properties.input_xaxis.tick_edit.input_ticks2.setText(
+ '10^-2\n0.1 "a"\n0.2 "b\n0.3 c\n0.5', signal=True
+ )
+
+ self.change_property2(
+ "xticks",
+ [0.01, 0.1, 0.2, 0.3, 0.5],
+ set_ticks,
+ get_axes,
+ line_command,
+ test_run,
+ test_saved_value=check_saved_property("x"),
+ get_function=lambda: get_axes().get_xticks(minor=True),
+ )
def set_ticks(_):
self.fig.window.input_properties.input_yaxis.tick_edit.setTarget(get_axes())
- self.fig.window.input_properties.input_yaxis.tick_edit.input_ticks2.setText('10^-2\n0.1 "a"\n0.2 "b\n0.3 c\n0.5',
- signal=True)
-
- self.change_property2("yticks", [0.01, 0.1, 0.2, 0.3, 0.5], set_ticks, get_axes, line_command, test_run,
- test_saved_value=check_saved_property("y"), get_function=lambda: get_axes().get_yticks(minor=True))
+ self.fig.window.input_properties.input_yaxis.tick_edit.input_ticks2.setText(
+ '10^-2\n0.1 "a"\n0.2 "b\n0.3 c\n0.5', signal=True
+ )
+
+ self.change_property2(
+ "yticks",
+ [0.01, 0.1, 0.2, 0.3, 0.5],
+ set_ticks,
+ get_axes,
+ line_command,
+ test_run,
+ test_saved_value=check_saved_property("y"),
+ get_function=lambda: get_axes().get_yticks(minor=True),
+ )
def test_axes_alignment(self):
# get the figure
@@ -227,9 +396,15 @@ def test_axes_alignment(self):
fig.change_tracker.addNewAxesChange(fig.axes[0])
fig.change_tracker.addNewAxesChange(fig.axes[1])
- self.change_property2("position", [([0.2, 0.2], [0.6, 0.6]), ([0.2, 0.1], [0.5, 0.4])],
- lambda _: fig.window.input_align.buttons[0].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[NotInSave, (0.2, 0.1, 0.3, 0.3)])
+ self.change_property2(
+ "position",
+ [([0.2, 0.2], [0.6, 0.6]), ([0.2, 0.1], [0.5, 0.4])],
+ lambda _: fig.window.input_align.buttons[0].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[NotInSave, (0.2, 0.1, 0.3, 0.3)],
+ )
# align center
fig.axes[0].set_position([0.2, 0.2, 0.4, 0.4])
@@ -237,9 +412,15 @@ def test_axes_alignment(self):
fig.change_tracker.addNewAxesChange(fig.axes[0])
fig.change_tracker.addNewAxesChange(fig.axes[1])
- self.change_property2("position", [([0.35, 0.2], [0.75, 0.6]), ([0.4, 0.1], [0.7, 0.4])],
- lambda _: fig.window.input_align.buttons[1].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.35, 0.2, 0.4, 0.4], [0.4, 0.1, 0.3, 0.3]])
+ self.change_property2(
+ "position",
+ [([0.35, 0.2], [0.75, 0.6]), ([0.4, 0.1], [0.7, 0.4])],
+ lambda _: fig.window.input_align.buttons[1].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[[0.35, 0.2, 0.4, 0.4], [0.4, 0.1, 0.3, 0.3]],
+ )
# align right
fig.axes[0].set_position([0.2, 0.2, 0.4, 0.4])
@@ -247,9 +428,15 @@ def test_axes_alignment(self):
fig.change_tracker.addNewAxesChange(fig.axes[0])
fig.change_tracker.addNewAxesChange(fig.axes[1])
- self.change_property2("position", [[[0.5, 0.2], [0.9, 0.6]], [[0.6, 0.1], [0.9, 0.4]]],
- lambda _: fig.window.input_align.buttons[2].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.5, 0.2, 0.4, 0.4], [0.6, 0.1, 0.3, 0.3]])
+ self.change_property2(
+ "position",
+ [[[0.5, 0.2], [0.9, 0.6]], [[0.6, 0.1], [0.9, 0.4]]],
+ lambda _: fig.window.input_align.buttons[2].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[[0.5, 0.2, 0.4, 0.4], [0.6, 0.1, 0.3, 0.3]],
+ )
# align top
fig.axes[0].set_position([0.2, 0.2, 0.4, 0.4])
@@ -257,9 +444,15 @@ def test_axes_alignment(self):
fig.change_tracker.addNewAxesChange(fig.axes[0])
fig.change_tracker.addNewAxesChange(fig.axes[1])
- self.change_property2("position", [[[0.2, 0.2], [0.6, 0.6]], [[0.6, 0.3], [0.9, 0.6]]],
- lambda _: fig.window.input_align.buttons[4].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.2, 0.2, 0.4, 0.4], [0.6, 0.3, 0.3, 0.3]])
+ self.change_property2(
+ "position",
+ [[[0.2, 0.2], [0.6, 0.6]], [[0.6, 0.3], [0.9, 0.6]]],
+ lambda _: fig.window.input_align.buttons[4].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[[0.2, 0.2, 0.4, 0.4], [0.6, 0.3, 0.3, 0.3]],
+ )
# align center
fig.axes[0].set_position([0.2, 0.2, 0.4, 0.4])
@@ -267,9 +460,15 @@ def test_axes_alignment(self):
fig.change_tracker.addNewAxesChange(fig.axes[0])
fig.change_tracker.addNewAxesChange(fig.axes[1])
- self.change_property2("position", [[[0.2, 0.15], [0.6, 0.55]], [[0.6, 0.2], [0.9, 0.5]]],
- lambda _: fig.window.input_align.buttons[5].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.2, 0.15, 0.4, 0.4], [0.6, 0.2, 0.3, 0.3]])
+ self.change_property2(
+ "position",
+ [[[0.2, 0.15], [0.6, 0.55]], [[0.6, 0.2], [0.9, 0.5]]],
+ lambda _: fig.window.input_align.buttons[5].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[[0.2, 0.15, 0.4, 0.4], [0.6, 0.2, 0.3, 0.3]],
+ )
# align bottom
fig.axes[0].set_position([0.2, 0.2, 0.4, 0.4])
@@ -277,16 +476,26 @@ def test_axes_alignment(self):
fig.change_tracker.addNewAxesChange(fig.axes[0])
fig.change_tracker.addNewAxesChange(fig.axes[1])
- self.change_property2("position", [[[0.2, 0.1], [0.6, 0.5]], [[0.6, 0.1], [0.9, 0.4]]],
- lambda _: fig.window.input_align.buttons[6].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.2, 0.1, 0.4, 0.4], [0.6, 0.1, 0.3, 0.3]])
+ self.change_property2(
+ "position",
+ [[[0.2, 0.1], [0.6, 0.5]], [[0.6, 0.1], [0.9, 0.4]]],
+ lambda _: fig.window.input_align.buttons[6].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[[0.2, 0.1, 0.4, 0.4], [0.6, 0.1, 0.3, 0.3]],
+ )
def test_axes_distribute(self):
# get the figure
fig, text = self.run_plot_script()
get_text = [lambda: fig.axes[0], lambda: fig.axes[1], lambda: fig.axes[2]]
- line_command = ["plt.figure(1).axes[0].set(", "plt.figure(1).axes[1].set(", "plt.figure(1).axes[2].set("]
+ line_command = [
+ "plt.figure(1).axes[0].set(",
+ "plt.figure(1).axes[1].set(",
+ "plt.figure(1).axes[2].set(",
+ ]
test_run = "Distribute axes."
# distribute X
@@ -298,11 +507,23 @@ def test_axes_distribute(self):
fig.change_tracker.addNewAxesChange(fig.axes[1])
fig.change_tracker.addNewAxesChange(fig.axes[2])
- self.change_property2("position", [[[0.2, 0.2], [0.5, 0.5]],
- [[0.7, 0.6], [0.9, 0.8]],
- [[0.4, 0.5], [0.8, 0.9]]],
- lambda _: fig.window.input_align.buttons[3].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.2, 0.2, 0.3, 0.3], [0.7, 0.6, 0.2, 0.2], [0.4, 0.5, 0.4, 0.4]])
+ self.change_property2(
+ "position",
+ [
+ [[0.2, 0.2], [0.5, 0.5]],
+ [[0.7, 0.6], [0.9, 0.8]],
+ [[0.4, 0.5], [0.8, 0.9]],
+ ],
+ lambda _: fig.window.input_align.buttons[3].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[
+ [0.2, 0.2, 0.3, 0.3],
+ [0.7, 0.6, 0.2, 0.2],
+ [0.4, 0.5, 0.4, 0.4],
+ ],
+ )
# distribute Y
fig.axes[0].set_position([0.2, 0.2, 0.3, 0.3])
@@ -313,18 +534,34 @@ def test_axes_distribute(self):
fig.change_tracker.addNewAxesChange(fig.axes[1])
fig.change_tracker.addNewAxesChange(fig.axes[2])
- self.change_property2("position", [[[0.2, 0.2], [0.5, 0.5]],
- [[0.6, 0.7], [0.8, 0.9]],
- [[0.5, 0.4], [0.9, 0.8]]],
- lambda _: fig.window.input_align.buttons[7].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.2, 0.2, 0.3, 0.3], [0.6, 0.7, 0.2, 0.2], [0.5, 0.4, 0.4, 0.4]])
+ self.change_property2(
+ "position",
+ [
+ [[0.2, 0.2], [0.5, 0.5]],
+ [[0.6, 0.7], [0.8, 0.9]],
+ [[0.5, 0.4], [0.9, 0.8]],
+ ],
+ lambda _: fig.window.input_align.buttons[7].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[
+ [0.2, 0.2, 0.3, 0.3],
+ [0.6, 0.7, 0.2, 0.2],
+ [0.5, 0.4, 0.4, 0.4],
+ ],
+ )
def test_axes_grid(self):
# get the figure
fig, text = self.run_plot_script()
get_text = [lambda: fig.axes[0], lambda: fig.axes[1], lambda: fig.axes[2]]
- line_command = ["plt.figure(1).axes[0].set(", "plt.figure(1).axes[1].set(", "plt.figure(1).axes[2].set("]
+ line_command = [
+ "plt.figure(1).axes[0].set(",
+ "plt.figure(1).axes[1].set(",
+ "plt.figure(1).axes[2].set(",
+ ]
test_run = "Distribute axes."
# distribute X
@@ -336,10 +573,20 @@ def test_axes_grid(self):
fig.change_tracker.addNewAxesChange(fig.axes[1])
fig.change_tracker.addNewAxesChange(fig.axes[2])
- self.change_property2("position", [[[0.2, 0.2], [0.45, 0.45]],
- [[0.2, 0.55], [0.45, 0.8]],
- [[0.5, 0.2], [0.75, 0.45]]],
- lambda _: fig.window.input_align.buttons[8].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[[0.2, 0.2, 0.25, 0.25],
- [0.2, 0.55, 0.25, 0.25],
- [0.5, 0.2, 0.25, 0.25]])
+ self.change_property2(
+ "position",
+ [
+ [[0.2, 0.2], [0.45, 0.45]],
+ [[0.2, 0.55], [0.45, 0.8]],
+ [[0.5, 0.2], [0.75, 0.45]],
+ ],
+ lambda _: fig.window.input_align.buttons[8].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[
+ [0.2, 0.2, 0.25, 0.25],
+ [0.2, 0.55, 0.25, 0.25],
+ [0.5, 0.2, 0.25, 0.25],
+ ],
+ )
diff --git a/tests/test_legend.py b/tests/test_legend.py
index b5202c9..b431165 100644
--- a/tests/test_legend.py
+++ b/tests/test_legend.py
@@ -1,10 +1,7 @@
-from matplotlib.backend_bases import KeyEvent
from base_test_class import BaseTest
-from matplotlib.legend import Legend
class TestLegend(BaseTest):
-
def test_legend_properties(self):
# get the figure
fig, text = self.run_plot_script()
@@ -12,7 +9,9 @@ def test_legend_properties(self):
fig.figure_dragger.select_element(fig.axes[2])
fig.window.input_properties.button_legend.clicked.emit()
- get_legend = lambda: fig.axes[2].get_legend()
+ def get_legend():
+ return fig.axes[2].get_legend()
+
line_command = "plt.figure(1).axes[2].legend("
test_run = "Change legend in axes."
x = 0.040748
@@ -20,80 +19,190 @@ def test_legend_properties(self):
self.move_element((0, 0), get_legend)
- #self.check_text_properties(get_text, line_command, test_run, 0.4931, 0.4979)
- self.change_property("loc", (x, 0.856602), lambda _: self.move_element((-1, 0)), get_legend, line_command,
- test_run, get_function=lambda: get_legend()._loc)
- self.change_property("loc", (0.047605, 0.856602), lambda _: self.move_element((1, 0)), get_legend, line_command,
- test_run, get_function=lambda: get_legend()._loc)
- self.change_property("loc", (0.047605, y), lambda _: self.move_element((0, -1)), get_legend, line_command,
- test_run, get_function=lambda: get_legend()._loc)
- self.change_property("loc", (0.047605, 0.856602), lambda _: self.move_element((0, 1)), get_legend, line_command,
- test_run, get_function=lambda: get_legend()._loc)
- #self.change_property("loc", (0.2, 0.5),
+ # self.check_text_properties(get_text, line_command, test_run, 0.4931, 0.4979)
+ self.change_property(
+ "loc",
+ (x, 0.856602),
+ lambda _: self.move_element((-1, 0)),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend()._loc,
+ )
+ self.change_property(
+ "loc",
+ (0.047605, 0.856602),
+ lambda _: self.move_element((1, 0)),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend()._loc,
+ )
+ self.change_property(
+ "loc",
+ (0.047605, y),
+ lambda _: self.move_element((0, -1)),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend()._loc,
+ )
+ self.change_property(
+ "loc",
+ (0.047605, 0.856602),
+ lambda _: self.move_element((0, 1)),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend()._loc,
+ )
+ # self.change_property("loc", (0.2, 0.5),
# lambda _: fig.window.input_size.input_position.valueChangedX.emit(0.2), get_text,
# line_command, test_run, get_function=lambda: get_text()._loc)
- #self.change_property("loc", (0.2, 0.2),
+ # self.change_property("loc", (0.2, 0.2),
# lambda _: fig.window.input_size.input_position.valueChangedY.emit(0.2), get_text,
# line_command, test_run, get_function=lambda: get_text()._loc)
- self.change_property("frameon", False,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "frameon"].setChecked(False, signal=True),
- get_legend, line_command, test_run, get_function=lambda: get_legend().get_frame_on())
-
- self.change_property("borderpad", 0.2,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "borderpad"].setValue(0.2),
- get_legend, line_command, test_run, get_function=lambda: get_legend().borderpad)
-
- self.change_property("labelspacing", 1.3,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "labelspacing"].setValue(1.3),
- get_legend, line_command, test_run, get_function=lambda: get_legend().labelspacing)
-
- self.change_property("markerscale", 3,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "markerscale"].setValue(3),
- get_legend, line_command, test_run, get_function=lambda: get_legend().markerscale)
-
- self.change_property("handlelength", 3,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "handlelength"].setValue(3),
- get_legend, line_command, test_run, get_function=lambda: get_legend().handlelength)
-
- self.change_property("handletextpad", 2,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "handletextpad"].setValue(2),
- get_legend, line_command, test_run, get_function=lambda: get_legend().handletextpad)
-
- self.change_property("ncols", 2,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "ncols"].setValue(2),
- get_legend, line_command, test_run, get_function=lambda: get_legend()._ncols)
-
- self.change_property("columnspacing", 2.3,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "columnspacing"].setValue(2.3),
- get_legend, line_command, test_run, get_function=lambda: get_legend().columnspacing)
-
- self.change_property("columnspacing", 2.3,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "columnspacing"].setValue(2.3),
- get_legend, line_command, test_run, get_function=lambda: get_legend().columnspacing)
-
- self.change_property("fontsize", 15,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "fontsize"].setValue(15),
- get_legend, line_command, test_run, get_function=lambda: get_legend()._fontsize)
-
- self.change_property("title", "new",
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "title"].setText("new", signal=True),
- get_legend, line_command, test_run, get_function=lambda: get_legend().get_title().get_text())
-
- self.change_property("title_fontsize", 23,
- lambda _: fig.window.input_properties.input_legend_properties.widgets[
- "title_fontsize"].setValue(23),
- get_legend, line_command, test_run, get_function=lambda: get_legend().get_title().get_fontsize())
-
-
+ self.change_property(
+ "frameon",
+ False,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "frameon"
+ ].setChecked(False, signal=True),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().get_frame_on(),
+ )
+
+ self.change_property(
+ "borderpad",
+ 0.2,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "borderpad"
+ ].setValue(0.2),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().borderpad,
+ )
+
+ self.change_property(
+ "labelspacing",
+ 1.3,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "labelspacing"
+ ].setValue(1.3),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().labelspacing,
+ )
+
+ self.change_property(
+ "markerscale",
+ 3,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "markerscale"
+ ].setValue(3),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().markerscale,
+ )
+
+ self.change_property(
+ "handlelength",
+ 3,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "handlelength"
+ ].setValue(3),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().handlelength,
+ )
+
+ self.change_property(
+ "handletextpad",
+ 2,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "handletextpad"
+ ].setValue(2),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().handletextpad,
+ )
+
+ self.change_property(
+ "ncols",
+ 2,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "ncols"
+ ].setValue(2),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend()._ncols,
+ )
+
+ self.change_property(
+ "columnspacing",
+ 2.3,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "columnspacing"
+ ].setValue(2.3),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().columnspacing,
+ )
+
+ self.change_property(
+ "columnspacing",
+ 2.3,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "columnspacing"
+ ].setValue(2.3),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().columnspacing,
+ )
+
+ self.change_property(
+ "fontsize",
+ 15,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "fontsize"
+ ].setValue(15),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend()._fontsize,
+ )
+
+ self.change_property(
+ "title",
+ "new",
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "title"
+ ].setText("new", signal=True),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().get_title().get_text(),
+ )
+
+ self.change_property(
+ "title_fontsize",
+ 23,
+ lambda _: fig.window.input_properties.input_legend_properties.widgets[
+ "title_fontsize"
+ ].setValue(23),
+ get_legend,
+ line_command,
+ test_run,
+ get_function=lambda: get_legend().get_title().get_fontsize(),
+ )
diff --git a/tests/test_text.py b/tests/test_text.py
index d2e67c8..121f45b 100644
--- a/tests/test_text.py
+++ b/tests/test_text.py
@@ -3,12 +3,13 @@
class TestText(BaseTest):
-
def test_text_properties_axes_existing(self):
# get the figure
fig, text = self.run_plot_script()
- get_text = lambda: fig.axes[0].texts[0]
+ def get_text():
+ return fig.axes[0].texts[0]
+
line_command = "plt.figure(1).axes[0].texts[0].set("
test_run = "Change existing text in axes."
self.check_text_properties(get_text, line_command, test_run, 0.4931, 0.497294)
@@ -20,7 +21,9 @@ def test_text_properties_axes_new(self):
fig.figure_dragger.select_element(fig.axes[0])
fig.window.input_properties.button_add_text.clicked.emit()
- get_text = lambda: fig.axes[0].texts[-1]
+ def get_text():
+ return fig.axes[0].texts[-1]
+
line_command = "plt.figure(1).axes[0].text("
test_run = "Change new text in axes."
@@ -30,7 +33,9 @@ def test_text_properties_figure_existing(self):
# get the figure
fig, text = self.run_plot_script()
- get_text = lambda: fig.texts[-1]
+ def get_text():
+ return fig.texts[-1]
+
line_command = "plt.figure(1).texts[0].set("
test_run = "Change existing text in Figure."
@@ -43,7 +48,9 @@ def test_text_properties_figure_new(self):
fig.figure_dragger.select_element(fig)
fig.window.input_properties.button_add_text.clicked.emit()
- get_text = lambda: fig.texts[-1]
+ def get_text():
+ return fig.texts[-1]
+
line_command = "plt.figure(1).text("
test_run = "Change new text in Figure."
@@ -57,7 +64,10 @@ def test_text_property_together(self):
fig.window.input_properties.button_add_text.clicked.emit()
get_text = [lambda: fig.axes[0].texts[0], lambda: fig.axes[0].texts[-1]]
- line_command = ["plt.figure(1).axes[0].texts[0].set(", "plt.figure(1).axes[0].text("]
+ line_command = [
+ "plt.figure(1).axes[0].texts[0].set(",
+ "plt.figure(1).axes[0].text(",
+ ]
test_run = "Change two texts together in axes."
self.check_text_properties(get_text, line_command, test_run, 0.493, 0.497)
@@ -70,7 +80,10 @@ def test_text_alignment(self):
fig.window.input_properties.button_add_text.clicked.emit()
get_text = [lambda: fig.axes[0].texts[0], lambda: fig.axes[0].texts[1]]
- line_command = ["plt.figure(1).axes[0].texts[0].set(", "plt.figure(1).axes[0].text("]
+ line_command = [
+ "plt.figure(1).axes[0].texts[0].set(",
+ "plt.figure(1).axes[0].text(",
+ ]
test_run = "Align texts in axes."
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
@@ -82,9 +95,15 @@ def test_text_alignment(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
- self.change_property2("position", [(0.2, 0.2), (0.2, 0.6)],
- lambda _: fig.window.input_align.buttons[0].clicked.emit(0), get_text, line_command,
- test_run, value2_list=[NotInSave, (0.2, 0.6)])
+ self.change_property2(
+ "position",
+ [(0.2, 0.2), (0.2, 0.6)],
+ lambda _: fig.window.input_align.buttons[0].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=[NotInSave, (0.2, 0.6)],
+ )
# align center
fig.axes[0].texts[0].set_position([0.2, 0.2])
@@ -92,9 +111,14 @@ def test_text_alignment(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
- self.change_property2("position", [(0.55, 0.2), (0.472, 0.6)],
- lambda _: fig.window.input_align.buttons[1].clicked.emit(0), get_text, line_command,
- test_run)
+ self.change_property2(
+ "position",
+ [(0.55, 0.2), (0.472, 0.6)],
+ lambda _: fig.window.input_align.buttons[1].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ )
# align right
fig.axes[0].texts[0].set_position([0.2, 0.2])
@@ -102,9 +126,14 @@ def test_text_alignment(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
- self.change_property2("position", [(0.834, 0.2), (0.6, 0.6)],
- lambda _: fig.window.input_align.buttons[2].clicked.emit(0), get_text, line_command,
- test_run)
+ self.change_property2(
+ "position",
+ [(0.834, 0.2), (0.6, 0.6)],
+ lambda _: fig.window.input_align.buttons[2].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ )
# align top
fig.axes[0].texts[0].set_position([0.2, 0.2])
@@ -112,9 +141,14 @@ def test_text_alignment(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
- self.change_property2("position", [(0.2, 0.6), (0.6, 0.6)],
- lambda _: fig.window.input_align.buttons[4].clicked.emit(0), get_text, line_command,
- test_run)
+ self.change_property2(
+ "position",
+ [(0.2, 0.6), (0.6, 0.6)],
+ lambda _: fig.window.input_align.buttons[4].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ )
# align center
fig.axes[0].texts[0].set_position([0.2, 0.2])
@@ -122,9 +156,14 @@ def test_text_alignment(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
- self.change_property2("position", [(0.2, 0.404), (0.6, 0.404)],
- lambda _: fig.window.input_align.buttons[5].clicked.emit(0), get_text, line_command,
- test_run)
+ self.change_property2(
+ "position",
+ [(0.2, 0.404), (0.6, 0.404)],
+ lambda _: fig.window.input_align.buttons[5].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ )
# align bottom
fig.axes[0].texts[0].set_position([0.2, 0.2])
@@ -132,9 +171,14 @@ def test_text_alignment(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
- self.change_property2("position", [(0.2, 0.2), (0.6, 0.2)],
- lambda _: fig.window.input_align.buttons[6].clicked.emit(0), get_text, line_command,
- test_run)
+ self.change_property2(
+ "position",
+ [(0.2, 0.2), (0.6, 0.2)],
+ lambda _: fig.window.input_align.buttons[6].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ )
def test_text_distribute(self):
# get the figure
@@ -147,7 +191,11 @@ def test_text_distribute(self):
fig.window.input_properties.button_add_text.clicked.emit()
get_text = [lambda: fig.axes[0].texts[0], lambda: fig.axes[0].texts[1]]
- line_command = ["plt.figure(1).axes[0].texts[0].set(", "plt.figure(1).axes[0].text(", "plt.figure(1).axes[0].text("]
+ line_command = [
+ "plt.figure(1).axes[0].texts[0].set(",
+ "plt.figure(1).axes[0].text(",
+ "plt.figure(1).axes[0].text(",
+ ]
test_run = "Distribute texts in axes."
fig.change_tracker.addNewTextChange(fig.axes[0].texts[0])
@@ -163,9 +211,14 @@ def test_text_distribute(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[2])
- self.change_property2("position", [(0.2, 0.2), (1.0301, 0.6), (0.5, 0.5)],
- lambda _: fig.window.input_align.buttons[3].clicked.emit(0), get_text, line_command,
- test_run)
+ self.change_property2(
+ "position",
+ [(0.2, 0.2), (1.0301, 0.6), (0.5, 0.5)],
+ lambda _: fig.window.input_align.buttons[3].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ )
# distribute Y
fig.axes[0].texts[0].set_position([0.2, 0.2])
@@ -175,9 +228,14 @@ def test_text_distribute(self):
fig.change_tracker.addNewTextChange(fig.axes[0].texts[1])
fig.change_tracker.addNewTextChange(fig.axes[0].texts[2])
- self.change_property2("position", [(0.2, 0.2), (0.6, 0.6460), (0.5, 0.5)],
- lambda _: fig.window.input_align.buttons[7].clicked.emit(0), get_text, line_command,
- test_run)
+ self.change_property2(
+ "position",
+ [(0.2, 0.2), (0.6, 0.6460), (0.5, 0.5)],
+ lambda _: fig.window.input_align.buttons[7].clicked.emit(0),
+ get_text,
+ line_command,
+ test_run,
+ )
def test_text_delete(self):
# get the figure
@@ -187,39 +245,187 @@ def test_text_delete(self):
fig.figure_dragger.select_element(fig.axes[0])
fig.window.input_properties.button_add_text.clicked.emit()
- get_text = lambda: fig.axes[0].texts[0]
+ def get_text():
+ return fig.axes[0].texts[0]
+
line_command = "plt.figure(1).axes[0].texts[0].set("
test_run = "Delete text in axes."
- self.change_property2("visible", False,
- lambda _: fig.figure_dragger.selection.keyPressEvent(KeyEvent('delete', fig.canvas, "delete")), get_text, line_command,
- test_run)
+ self.change_property2(
+ "visible",
+ False,
+ lambda _: fig.figure_dragger.selection.keyPressEvent(
+ KeyEvent("delete", fig.canvas, "delete")
+ ),
+ get_text,
+ line_command,
+ test_run,
+ )
+
+ def get_text():
+ return fig.axes[0].texts[1]
- get_text = lambda: fig.axes[0].texts[1]
line_command = "plt.figure(1).axes[0].text("
test_run = "Delete new text in axes."
- self.change_property2("visible", False,
- lambda _: fig.figure_dragger.selection.keyPressEvent(
- KeyEvent('delete', fig.canvas, "delete")), get_text, line_command,
- test_run, delete=True)
+ self.change_property2(
+ "visible",
+ False,
+ lambda _: fig.figure_dragger.selection.keyPressEvent(
+ KeyEvent("delete", fig.canvas, "delete")
+ ),
+ get_text,
+ line_command,
+ test_run,
+ delete=True,
+ )
def check_text_properties(self, get_text, line_command, test_run, x, y):
fig = self.fig
- self.change_property2("position", (x, 0.5), lambda _: self.move_element((-1, 0)), get_text, line_command, test_run)
+ self.change_property2(
+ "position",
+ (x, 0.5),
+ lambda _: self.move_element((-1, 0)),
+ get_text,
+ line_command,
+ test_run,
+ )
self.move_element((1, 0), get_text)
- self.change_property2("position", (0.5, y), lambda _: self.move_element((0, -1)), get_text, line_command, test_run)
+ self.change_property2(
+ "position",
+ (0.5, y),
+ lambda _: self.move_element((0, -1)),
+ get_text,
+ line_command,
+ test_run,
+ )
self.move_element((0, 1), get_text)
- self.change_property2("position", (0.2, 0.5), lambda _: fig.window.input_size.input_position.valueChangedX.emit(0.2), get_text, line_command, test_run)
- self.change_property2("position", (0.2, 0.2), lambda _: fig.window.input_size.input_position.valueChangedY.emit(0.2), get_text, line_command, test_run)
- self.change_property2("weight", "bold", lambda _: fig.window.input_properties.input_font_properties.button_bold.clicked.emit(True), get_text, line_command, test_run)
- self.change_property2("weight", "normal", lambda _: fig.window.input_properties.input_font_properties.button_bold.clicked.emit(False), get_text, line_command, test_run, value2_list=None)
- self.change_property2("style", "italic", lambda _: fig.window.input_properties.input_font_properties.button_italic.clicked.emit(True), get_text, line_command, test_run)
- self.change_property2("style", "normal", lambda _: fig.window.input_properties.input_font_properties.button_italic.clicked.emit(False), get_text, line_command, test_run, value2_list=None)
- self.change_property2("ha", "left", lambda _: fig.window.input_properties.input_font_properties.buttons_align[0].clicked.emit(True), get_text, line_command, test_run, value2_list=None)
- self.change_property2("ha", "center", lambda _: fig.window.input_properties.input_font_properties.buttons_align[1].clicked.emit(True), get_text, line_command, test_run)
- self.change_property2("ha", "right", lambda _: fig.window.input_properties.input_font_properties.buttons_align[2].clicked.emit(True), get_text, line_command, test_run)
- self.change_property2("color", "#FF0000", lambda _: fig.window.input_properties.input_font_properties.button_color.valueChanged.emit("#FF0000"), get_text, line_command, test_run)
- self.change_property2("fontsize", 8, lambda _: fig.window.input_properties.input_font_properties.font_size.valueChanged.emit(8), get_text, line_command, test_run)
- self.change_property2("text", "update", lambda _: fig.window.input_properties.input_text.setText("update", signal=True), get_text, line_command, test_run)
- self.change_property2("rotation", 45, lambda _: fig.window.input_properties.input_rotation.setValue(45, signal=True), get_text, line_command, test_run)
+ self.change_property2(
+ "position",
+ (0.2, 0.5),
+ lambda _: fig.window.input_size.input_position.valueChangedX.emit(0.2),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "position",
+ (0.2, 0.2),
+ lambda _: fig.window.input_size.input_position.valueChangedY.emit(0.2),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "weight",
+ "bold",
+ lambda _: fig.window.input_properties.input_font_properties.button_bold.clicked.emit(
+ True
+ ),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "weight",
+ "normal",
+ lambda _: fig.window.input_properties.input_font_properties.button_bold.clicked.emit(
+ False
+ ),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=None,
+ )
+ self.change_property2(
+ "style",
+ "italic",
+ lambda _: fig.window.input_properties.input_font_properties.button_italic.clicked.emit(
+ True
+ ),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "style",
+ "normal",
+ lambda _: fig.window.input_properties.input_font_properties.button_italic.clicked.emit(
+ False
+ ),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=None,
+ )
+ self.change_property2(
+ "ha",
+ "left",
+ lambda _: fig.window.input_properties.input_font_properties.buttons_align[
+ 0
+ ].clicked.emit(True),
+ get_text,
+ line_command,
+ test_run,
+ value2_list=None,
+ )
+ self.change_property2(
+ "ha",
+ "center",
+ lambda _: fig.window.input_properties.input_font_properties.buttons_align[
+ 1
+ ].clicked.emit(True),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "ha",
+ "right",
+ lambda _: fig.window.input_properties.input_font_properties.buttons_align[
+ 2
+ ].clicked.emit(True),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "color",
+ "#FF0000",
+ lambda _: fig.window.input_properties.input_font_properties.button_color.valueChanged.emit(
+ "#FF0000"
+ ),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "fontsize",
+ 8,
+ lambda _: fig.window.input_properties.input_font_properties.font_size.valueChanged.emit(
+ 8
+ ),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "text",
+ "update",
+ lambda _: fig.window.input_properties.input_text.setText(
+ "update", signal=True
+ ),
+ get_text,
+ line_command,
+ test_run,
+ )
+ self.change_property2(
+ "rotation",
+ 45,
+ lambda _: fig.window.input_properties.input_rotation.setValue(
+ 45, signal=True
+ ),
+ get_text,
+ line_command,
+ test_run,
+ )
diff --git a/ty.toml b/ty.toml
new file mode 100644
index 0000000..a131aa2
--- /dev/null
+++ b/ty.toml
@@ -0,0 +1,5 @@
+[environment]
+python-version = "3.9"
+
+[src]
+exclude = ["docs/"]
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..5bd674c
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,3650 @@
+version = 1
+revision = 3
+requires-python = ">=3.9"
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+ "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "alabaster"
+version = "0.7.16"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" },
+]
+
+[[package]]
+name = "alabaster"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
+]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.14.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "soupsieve" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
+]
+
+[[package]]
+name = "bleach"
+version = "6.2.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "webencodings", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083, upload-time = "2024-10-29T18:30:40.477Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406, upload-time = "2024-10-29T18:30:38.186Z" },
+]
+
+[package.optional-dependencies]
+css = [
+ { name = "tinycss2", marker = "python_full_version < '3.10'" },
+]
+
+[[package]]
+name = "bleach"
+version = "6.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "webencodings", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" },
+]
+
+[package.optional-dependencies]
+css = [
+ { name = "tinycss2", marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" },
+ { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" },
+ { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" },
+ { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" },
+ { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" },
+ { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" },
+ { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" },
+ { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" },
+ { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" },
+ { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
+ { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
+ { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
+ { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
+ { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
+ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
+ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
+ { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
+ { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" },
+ { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" },
+ { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" },
+ { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" },
+ { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" },
+ { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" },
+ { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" },
+ { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" },
+ { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" },
+ { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" },
+ { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" },
+ { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" },
+ { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" },
+ { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" },
+ { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" },
+ { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
+ { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
+ { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
+ { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
+ { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
+ { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
+ { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
+ { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
+ { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
+ { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
+ { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
+ { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
+ { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
+ { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
+ { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
+ { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
+ { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" },
+ { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" },
+ { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" },
+ { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" },
+ { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" },
+ { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" },
+ { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "contourpy"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370, upload-time = "2024-08-27T21:00:03.328Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6c/e0/be8dcc796cfdd96708933e0e2da99ba4bb8f9b2caa9d560a50f3f09a65f3/contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7", size = 265366, upload-time = "2024-08-27T20:50:09.947Z" },
+ { url = "https://files.pythonhosted.org/packages/50/d6/c953b400219443535d412fcbbc42e7a5e823291236bc0bb88936e3cc9317/contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42", size = 249226, upload-time = "2024-08-27T20:50:16.1Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/b4/6fffdf213ffccc28483c524b9dad46bb78332851133b36ad354b856ddc7c/contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7", size = 308460, upload-time = "2024-08-27T20:50:22.536Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/6c/118fc917b4050f0afe07179a6dcbe4f3f4ec69b94f36c9e128c4af480fb8/contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab", size = 347623, upload-time = "2024-08-27T20:50:28.806Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/a4/30ff110a81bfe3abf7b9673284d21ddce8cc1278f6f77393c91199da4c90/contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589", size = 317761, upload-time = "2024-08-27T20:50:35.126Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e6/d11966962b1aa515f5586d3907ad019f4b812c04e4546cc19ebf62b5178e/contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41", size = 322015, upload-time = "2024-08-27T20:50:40.318Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/e3/182383743751d22b7b59c3c753277b6aee3637049197624f333dac5b4c80/contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d", size = 1262672, upload-time = "2024-08-27T20:50:55.643Z" },
+ { url = "https://files.pythonhosted.org/packages/78/53/974400c815b2e605f252c8fb9297e2204347d1755a5374354ee77b1ea259/contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223", size = 1321688, upload-time = "2024-08-27T20:51:11.293Z" },
+ { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145, upload-time = "2024-08-27T20:51:15.2Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019, upload-time = "2024-08-27T20:51:19.365Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356, upload-time = "2024-08-27T20:51:24.146Z" },
+ { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915, upload-time = "2024-08-27T20:51:28.683Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443, upload-time = "2024-08-27T20:51:33.675Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/c2/1a612e475492e07f11c8e267ea5ec1ce0d89971be496c195e27afa97e14a/contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081", size = 348548, upload-time = "2024-08-27T20:51:39.322Z" },
+ { url = "https://files.pythonhosted.org/packages/45/cf/2c2fc6bb5874158277b4faf136847f0689e1b1a1f640a36d76d52e78907c/contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1", size = 319118, upload-time = "2024-08-27T20:51:44.717Z" },
+ { url = "https://files.pythonhosted.org/packages/03/33/003065374f38894cdf1040cef474ad0546368eea7e3a51d48b8a423961f8/contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d", size = 323162, upload-time = "2024-08-27T20:51:49.683Z" },
+ { url = "https://files.pythonhosted.org/packages/42/80/e637326e85e4105a802e42959f56cff2cd39a6b5ef68d5d9aee3ea5f0e4c/contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c", size = 1265396, upload-time = "2024-08-27T20:52:04.926Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/3b/8cbd6416ca1bbc0202b50f9c13b2e0b922b64be888f9d9ee88e6cfabfb51/contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb", size = 1324297, upload-time = "2024-08-27T20:52:21.843Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808, upload-time = "2024-08-27T20:52:25.163Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181, upload-time = "2024-08-27T20:52:29.13Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/92/8e0bbfe6b70c0e2d3d81272b58c98ac69ff1a4329f18c73bd64824d8b12e/contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f", size = 267838, upload-time = "2024-08-27T20:52:33.911Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/04/33351c5d5108460a8ce6d512307690b023f0cfcad5899499f5c83b9d63b1/contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6", size = 251549, upload-time = "2024-08-27T20:52:39.179Z" },
+ { url = "https://files.pythonhosted.org/packages/51/3d/aa0fe6ae67e3ef9f178389e4caaaa68daf2f9024092aa3c6032e3d174670/contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639", size = 303177, upload-time = "2024-08-27T20:52:44.789Z" },
+ { url = "https://files.pythonhosted.org/packages/56/c3/c85a7e3e0cab635575d3b657f9535443a6f5d20fac1a1911eaa4bbe1aceb/contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c", size = 341735, upload-time = "2024-08-27T20:52:51.05Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/8d/20f7a211a7be966a53f474bc90b1a8202e9844b3f1ef85f3ae45a77151ee/contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06", size = 314679, upload-time = "2024-08-27T20:52:58.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/be/524e377567defac0e21a46e2a529652d165fed130a0d8a863219303cee18/contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09", size = 320549, upload-time = "2024-08-27T20:53:06.593Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/96/fdb2552a172942d888915f3a6663812e9bc3d359d53dafd4289a0fb462f0/contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd", size = 1263068, upload-time = "2024-08-27T20:53:23.442Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/25/632eab595e3140adfa92f1322bf8915f68c932bac468e89eae9974cf1c00/contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35", size = 1322833, upload-time = "2024-08-27T20:53:39.243Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681, upload-time = "2024-08-27T20:53:43.05Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283, upload-time = "2024-08-27T20:53:47.232Z" },
+ { url = "https://files.pythonhosted.org/packages/53/a1/d20415febfb2267af2d7f06338e82171824d08614084714fb2c1dac9901f/contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3", size = 267879, upload-time = "2024-08-27T20:53:51.597Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/45/5a28a3570ff6218d8bdfc291a272a20d2648104815f01f0177d103d985e1/contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7", size = 251573, upload-time = "2024-08-27T20:53:55.659Z" },
+ { url = "https://files.pythonhosted.org/packages/39/1c/d3f51540108e3affa84f095c8b04f0aa833bb797bc8baa218a952a98117d/contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84", size = 303184, upload-time = "2024-08-27T20:54:00.225Z" },
+ { url = "https://files.pythonhosted.org/packages/00/56/1348a44fb6c3a558c1a3a0cd23d329d604c99d81bf5a4b58c6b71aab328f/contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0", size = 340262, upload-time = "2024-08-27T20:54:05.234Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/23/00d665ba67e1bb666152131da07e0f24c95c3632d7722caa97fb61470eca/contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b", size = 313806, upload-time = "2024-08-27T20:54:09.889Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/42/3cf40f7040bb8362aea19af9a5fb7b32ce420f645dd1590edcee2c657cd5/contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da", size = 319710, upload-time = "2024-08-27T20:54:14.536Z" },
+ { url = "https://files.pythonhosted.org/packages/05/32/f3bfa3fc083b25e1a7ae09197f897476ee68e7386e10404bdf9aac7391f0/contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14", size = 1264107, upload-time = "2024-08-27T20:54:29.735Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/1e/1019d34473a736664f2439542b890b2dc4c6245f5c0d8cdfc0ccc2cab80c/contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8", size = 1322458, upload-time = "2024-08-27T20:54:45.507Z" },
+ { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643, upload-time = "2024-08-27T20:55:52.754Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301, upload-time = "2024-08-27T20:55:56.509Z" },
+ { url = "https://files.pythonhosted.org/packages/76/65/702f4064f397821fea0cb493f7d3bc95a5d703e20954dce7d6d39bacf378/contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8", size = 278972, upload-time = "2024-08-27T20:54:50.347Z" },
+ { url = "https://files.pythonhosted.org/packages/80/85/21f5bba56dba75c10a45ec00ad3b8190dbac7fd9a8a8c46c6116c933e9cf/contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b", size = 263375, upload-time = "2024-08-27T20:54:54.909Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/64/084c86ab71d43149f91ab3a4054ccf18565f0a8af36abfa92b1467813ed6/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973", size = 307188, upload-time = "2024-08-27T20:55:00.184Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ff/d61a4c288dc42da0084b8d9dc2aa219a850767165d7d9a9c364ff530b509/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18", size = 345644, upload-time = "2024-08-27T20:55:05.673Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/aa/00d2313d35ec03f188e8f0786c2fc61f589306e02fdc158233697546fd58/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8", size = 317141, upload-time = "2024-08-27T20:55:11.047Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/6a/b5242c8cb32d87f6abf4f5e3044ca397cb1a76712e3fa2424772e3ff495f/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6", size = 323469, upload-time = "2024-08-27T20:55:15.914Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/a6/73e929d43028a9079aca4bde107494864d54f0d72d9db508a51ff0878593/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2", size = 1260894, upload-time = "2024-08-27T20:55:31.553Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/1e/1e726ba66eddf21c940821df8cf1a7d15cb165f0682d62161eaa5e93dae1/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927", size = 1314829, upload-time = "2024-08-27T20:55:47.837Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/e3/b9f72758adb6ef7397327ceb8b9c39c75711affb220e4f53c745ea1d5a9a/contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8", size = 265518, upload-time = "2024-08-27T20:56:01.333Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/22/19f5b948367ab5260fb41d842c7a78dae645603881ea6bc39738bcfcabf6/contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c", size = 249350, upload-time = "2024-08-27T20:56:05.432Z" },
+ { url = "https://files.pythonhosted.org/packages/26/76/0c7d43263dd00ae21a91a24381b7e813d286a3294d95d179ef3a7b9fb1d7/contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca", size = 309167, upload-time = "2024-08-27T20:56:10.034Z" },
+ { url = "https://files.pythonhosted.org/packages/96/3b/cadff6773e89f2a5a492c1a8068e21d3fccaf1a1c1df7d65e7c8e3ef60ba/contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f", size = 348279, upload-time = "2024-08-27T20:56:15.41Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/86/158cc43aa549d2081a955ab11c6bdccc7a22caacc2af93186d26f5f48746/contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc", size = 318519, upload-time = "2024-08-27T20:56:21.813Z" },
+ { url = "https://files.pythonhosted.org/packages/05/11/57335544a3027e9b96a05948c32e566328e3a2f84b7b99a325b7a06d2b06/contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2", size = 321922, upload-time = "2024-08-27T20:56:26.983Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/e3/02114f96543f4a1b694333b92a6dcd4f8eebbefcc3a5f3bbb1316634178f/contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e", size = 1258017, upload-time = "2024-08-27T20:56:42.246Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/3b/bfe4c81c6d5881c1c643dde6620be0b42bf8aab155976dd644595cfab95c/contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800", size = 1316773, upload-time = "2024-08-27T20:56:58.58Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/17/c52d2970784383cafb0bd918b6fb036d98d96bbf0bc1befb5d1e31a07a70/contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5", size = 171353, upload-time = "2024-08-27T20:57:02.718Z" },
+ { url = "https://files.pythonhosted.org/packages/53/23/db9f69676308e094d3c45f20cc52e12d10d64f027541c995d89c11ad5c75/contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843", size = 211817, upload-time = "2024-08-27T20:57:06.328Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/09/60e486dc2b64c94ed33e58dcfb6f808192c03dfc5574c016218b9b7680dc/contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c", size = 261886, upload-time = "2024-08-27T20:57:10.863Z" },
+ { url = "https://files.pythonhosted.org/packages/19/20/b57f9f7174fcd439a7789fb47d764974ab646fa34d1790551de386457a8e/contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779", size = 311008, upload-time = "2024-08-27T20:57:15.588Z" },
+ { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690, upload-time = "2024-08-27T20:57:19.321Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/24/dc3dcd77ac7460ab7e9d2b01a618cb31406902e50e605a8d6091f0a8f7cc/contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0", size = 261894, upload-time = "2024-08-27T20:57:23.873Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/db/531642a01cfec39d1682e46b5457b07cf805e3c3c584ec27e2a6223f8f6c/contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102", size = 311099, upload-time = "2024-08-27T20:57:28.58Z" },
+ { url = "https://files.pythonhosted.org/packages/38/1e/94bda024d629f254143a134eead69e21c836429a2a6ce82209a00ddcb79a/contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb", size = 215838, upload-time = "2024-08-27T20:57:32.913Z" },
+]
+
+[[package]]
+name = "contourpy"
+version = "1.3.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" },
+ { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" },
+ { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" },
+ { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" },
+ { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" },
+ { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" },
+ { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" },
+ { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" },
+ { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" },
+ { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" },
+ { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" },
+ { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" },
+ { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" },
+ { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" },
+ { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" },
+ { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" },
+ { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" },
+ { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" },
+ { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" },
+ { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" },
+ { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" },
+]
+
+[[package]]
+name = "contourpy"
+version = "1.3.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+]
+dependencies = [
+ { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" },
+ { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" },
+ { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" },
+ { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" },
+ { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" },
+ { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" },
+ { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" },
+ { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" },
+ { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" },
+ { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" },
+ { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" },
+ { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" },
+ { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" },
+ { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" },
+ { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" },
+ { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" },
+ { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" },
+ { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" },
+ { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" },
+ { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" },
+ { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" },
+ { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" },
+ { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" },
+ { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" },
+ { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" },
+ { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" },
+ { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" },
+ { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" },
+ { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" },
+ { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" },
+ { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" },
+ { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" },
+ { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" },
+]
+
+[[package]]
+name = "cycler"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
+]
+
+[[package]]
+name = "docutils"
+version = "0.21.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
+]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.21.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" },
+]
+
+[[package]]
+name = "fonttools"
+version = "4.60.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3e/c4/db6a7b5eb0656534c3aa2596c2c5e18830d74f1b9aa5aa8a7dff63a0b11d/fonttools-4.60.2.tar.gz", hash = "sha256:d29552e6b155ebfc685b0aecf8d429cb76c14ab734c22ef5d3dea6fdf800c92c", size = 3562254, upload-time = "2025-12-09T13:38:11.835Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ab/de/9e10a99fb3070accb8884886a41a4ce54e49bf2fa4fc63f48a6cf2061713/fonttools-4.60.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e36fadcf7e8ca6e34d490eef86ed638d6fd9c55d2f514b05687622cfc4a7050", size = 2850403, upload-time = "2025-12-09T13:35:53.14Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/40/d5b369d1073b134f600a94a287e13b5bdea2191ba6347d813fa3da00e94a/fonttools-4.60.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e500fc9c04bee749ceabfc20cb4903f6981c2139050d85720ea7ada61b75d5c", size = 2398629, upload-time = "2025-12-09T13:35:56.471Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/b5/123819369aaf99d1e4dc49f1de1925d4edc7379114d15a56a7dd2e9d56e6/fonttools-4.60.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22efea5e784e1d1cd8d7b856c198e360a979383ebc6dea4604743b56da1cbc34", size = 4893471, upload-time = "2025-12-09T13:35:58.927Z" },
+ { url = "https://files.pythonhosted.org/packages/24/29/f8f8acccb9716b899be4be45e9ce770d6aa76327573863e68448183091b0/fonttools-4.60.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:677aa92d84d335e4d301d8ba04afca6f575316bc647b6782cb0921943fcb6343", size = 4854686, upload-time = "2025-12-09T13:36:01.767Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/0d/f3f51d7519f44f2dd5c9a60d7cd41185ebcee4348f073e515a3a93af15ff/fonttools-4.60.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:edd49d3defbf35476e78b61ff737ff5efea811acff68d44233a95a5a48252334", size = 4871233, upload-time = "2025-12-09T13:36:06.094Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/3f/4d4fd47d3bc40ab4d76718555185f8adffb5602ea572eac4bbf200c47d22/fonttools-4.60.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:126839492b69cecc5baf2bddcde60caab2ffafd867bbae2a88463fce6078ca3a", size = 4988936, upload-time = "2025-12-09T13:36:08.42Z" },
+ { url = "https://files.pythonhosted.org/packages/01/6f/83bbdefa43f2c3ae206fd8c4b9a481f3c913eef871b1ce9a453069239e39/fonttools-4.60.2-cp310-cp310-win32.whl", hash = "sha256:ffcab6f5537136046ca902ed2491ab081ba271b07591b916289b7c27ff845f96", size = 2278044, upload-time = "2025-12-09T13:36:10.641Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/04/7d9a137e919d6c9ef26704b7f7b2580d9cfc5139597588227aacebc0e3b7/fonttools-4.60.2-cp310-cp310-win_amd64.whl", hash = "sha256:9c68b287c7ffcd29dd83b5f961004b2a54a862a88825d52ea219c6220309ba45", size = 2326522, upload-time = "2025-12-09T13:36:12.981Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/80/b7693d37c02417e162cc83cdd0b19a4f58be82c638b5d4ce4de2dae050c4/fonttools-4.60.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2aed0a7931401b3875265717a24c726f87ecfedbb7b3426c2ca4d2812e281ae", size = 2847809, upload-time = "2025-12-09T13:36:14.884Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/9a/9c2c13bf8a6496ac21607d704e74e9cc68ebf23892cf924c9a8b5c7566b9/fonttools-4.60.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea6868e9d2b816c9076cfea77754686f3c19149873bdbc5acde437631c15df1", size = 2397302, upload-time = "2025-12-09T13:36:17.151Z" },
+ { url = "https://files.pythonhosted.org/packages/56/f6/ce38ff6b2d2d58f6fd981d32f3942365bfa30eadf2b47d93b2d48bf6097f/fonttools-4.60.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fa27f34950aa1fe0f0b1abe25eed04770a3b3b34ad94e5ace82cc341589678a", size = 5054418, upload-time = "2025-12-09T13:36:19.062Z" },
+ { url = "https://files.pythonhosted.org/packages/88/06/5353bea128ff39e857c31de3dd605725b4add956badae0b31bc9a50d4c8e/fonttools-4.60.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13a53d479d187b09bfaa4a35ffcbc334fc494ff355f0a587386099cb66674f1e", size = 5031652, upload-time = "2025-12-09T13:36:21.206Z" },
+ { url = "https://files.pythonhosted.org/packages/71/05/ebca836437f6ebd57edd6428e7eff584e683ff0556ddb17d62e3b731f46c/fonttools-4.60.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fac5e921d3bd0ca3bb8517dced2784f0742bc8ca28579a68b139f04ea323a779", size = 5030321, upload-time = "2025-12-09T13:36:23.515Z" },
+ { url = "https://files.pythonhosted.org/packages/57/f9/eb9d2a2ce30c99f840c1cc3940729a970923cf39d770caf88909d98d516b/fonttools-4.60.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:648f4f9186fd7f1f3cd57dbf00d67a583720d5011feca67a5e88b3a491952cfb", size = 5154255, upload-time = "2025-12-09T13:36:25.879Z" },
+ { url = "https://files.pythonhosted.org/packages/08/a2/088b6ceba8272a9abb629d3c08f9c1e35e5ce42db0ccfe0c1f9f03e60d1d/fonttools-4.60.2-cp311-cp311-win32.whl", hash = "sha256:3274e15fad871bead5453d5ce02658f6d0c7bc7e7021e2a5b8b04e2f9e40da1a", size = 2276300, upload-time = "2025-12-09T13:36:27.772Z" },
+ { url = "https://files.pythonhosted.org/packages/de/2f/8e4c3d908cc5dade7bb1316ce48589f6a24460c1056fd4b8db51f1fa309a/fonttools-4.60.2-cp311-cp311-win_amd64.whl", hash = "sha256:91d058d5a483a1525b367803abb69de0923fbd45e1f82ebd000f5c8aa65bc78e", size = 2327574, upload-time = "2025-12-09T13:36:30.89Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/30/530c9eddcd1c39219dc0aaede2b5a4c8ab80e0bb88d1b3ffc12944c4aac3/fonttools-4.60.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e0164b7609d2b5c5dd4e044b8085b7bd7ca7363ef8c269a4ab5b5d4885a426b2", size = 2847196, upload-time = "2025-12-09T13:36:33.262Z" },
+ { url = "https://files.pythonhosted.org/packages/19/2f/4077a482836d5bbe3bc9dac1c004d02ee227cf04ed62b0a2dfc41d4f0dfd/fonttools-4.60.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1dd3d9574fc595c1e97faccae0f264dc88784ddf7fbf54c939528378bacc0033", size = 2395842, upload-time = "2025-12-09T13:36:35.47Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/05/aae5bb99c5398f8ed4a8b784f023fd9dd3568f0bd5d5b21e35b282550f11/fonttools-4.60.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98d0719f1b11c2817307d2da2e94296a3b2a3503f8d6252a101dca3ee663b917", size = 4949713, upload-time = "2025-12-09T13:36:37.874Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/37/49067349fc78ff0efbf09fadefe80ddf41473ca8f8a25400e3770da38328/fonttools-4.60.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d3ea26957dd07209f207b4fff64c702efe5496de153a54d3b91007ec28904dd", size = 4999907, upload-time = "2025-12-09T13:36:39.853Z" },
+ { url = "https://files.pythonhosted.org/packages/16/31/d0f11c758bd0db36b664c92a0f9dfdcc2d7313749aa7d6629805c6946f21/fonttools-4.60.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ee301273b0850f3a515299f212898f37421f42ff9adfc341702582ca5073c13", size = 4939717, upload-time = "2025-12-09T13:36:43.075Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/bc/1cff0d69522e561bf1b99bee7c3911c08c25e919584827c3454a64651ce9/fonttools-4.60.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6eb4694cc3b9c03b7c01d65a9cf35b577f21aa6abdbeeb08d3114b842a58153", size = 5089205, upload-time = "2025-12-09T13:36:45.468Z" },
+ { url = "https://files.pythonhosted.org/packages/05/e6/fb174f0069b7122e19828c551298bfd34fdf9480535d2a6ac2ed37afacd3/fonttools-4.60.2-cp312-cp312-win32.whl", hash = "sha256:57f07b616c69c244cc1a5a51072eeef07dddda5ebef9ca5c6e9cf6d59ae65b70", size = 2264674, upload-time = "2025-12-09T13:36:49.238Z" },
+ { url = "https://files.pythonhosted.org/packages/75/57/6552ffd6b582d3e6a9f01780c5275e6dfff1e70ca146101733aa1c12a129/fonttools-4.60.2-cp312-cp312-win_amd64.whl", hash = "sha256:310035802392f1fe5a7cf43d76f6ff4a24c919e4c72c0352e7b8176e2584b8a0", size = 2314701, upload-time = "2025-12-09T13:36:51.09Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e4/8381d0ca6b6c6c484660b03517ec5b5b81feeefca3808726dece36c652a9/fonttools-4.60.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2bb5fd231e56ccd7403212636dcccffc96c5ae0d6f9e4721fa0a32cb2e3ca432", size = 2842063, upload-time = "2025-12-09T13:36:53.468Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/2c/4367117ee8ff4f4374787a1222da0bd413d80cf3522111f727a7b8f80d1d/fonttools-4.60.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:536b5fab7b6fec78ccf59b5c59489189d9d0a8b0d3a77ed1858be59afb096696", size = 2393792, upload-time = "2025-12-09T13:36:55.742Z" },
+ { url = "https://files.pythonhosted.org/packages/49/b7/a76b6dffa193869e54e32ca2f9abb0d0e66784bc8a24e6f86eb093015481/fonttools-4.60.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b9288fc38252ac86a9570f19313ecbc9ff678982e0f27c757a85f1f284d3400", size = 4924020, upload-time = "2025-12-09T13:36:58.229Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/4e/0078200e2259f0061c86a74075f507d64c43dd2ab38971956a5c0012d344/fonttools-4.60.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93fcb420791d839ef592eada2b69997c445d0ce9c969b5190f2e16828ec10607", size = 4980070, upload-time = "2025-12-09T13:37:00.311Z" },
+ { url = "https://files.pythonhosted.org/packages/85/1f/d87c85a11cb84852c975251581862681e4a0c1c3bd456c648792203f311b/fonttools-4.60.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7916a381b094db4052ac284255186aebf74c5440248b78860cb41e300036f598", size = 4921411, upload-time = "2025-12-09T13:37:02.345Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c0/7efad650f5ed8e317c2633133ef3c64917e7adf2e4e2940c798f5d57ec6e/fonttools-4.60.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58c8c393d5e16b15662cfc2d988491940458aa87894c662154f50c7b49440bef", size = 5063465, upload-time = "2025-12-09T13:37:04.836Z" },
+ { url = "https://files.pythonhosted.org/packages/18/a8/750518c4f8cdd79393b386bc81226047ade80239e58c6c9f5dbe1fdd8ea1/fonttools-4.60.2-cp313-cp313-win32.whl", hash = "sha256:19c6e0afd8b02008caa0aa08ab896dfce5d0bcb510c49b2c499541d5cb95a963", size = 2263443, upload-time = "2025-12-09T13:37:06.762Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/22/026c60376f165981f80a0e90bd98a79ae3334e9d89a3d046c4d2e265c724/fonttools-4.60.2-cp313-cp313-win_amd64.whl", hash = "sha256:6a500dc59e11b2338c2dba1f8cf11a4ae8be35ec24af8b2628b8759a61457b76", size = 2313800, upload-time = "2025-12-09T13:37:08.713Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/ab/7cf1f5204e1366ddf9dc5cdc2789b571feb9eebcee0e3463c3f457df5f52/fonttools-4.60.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9387c532acbe323bbf2a920f132bce3c408a609d5f9dcfc6532fbc7e37f8ccbb", size = 2841690, upload-time = "2025-12-09T13:37:10.696Z" },
+ { url = "https://files.pythonhosted.org/packages/00/3c/0bf83c6f863cc8b934952567fa2bf737cfcec8fc4ffb59b3f93820095f89/fonttools-4.60.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6f1c824185b5b8fb681297f315f26ae55abb0d560c2579242feea8236b1cfef", size = 2392191, upload-time = "2025-12-09T13:37:12.954Z" },
+ { url = "https://files.pythonhosted.org/packages/00/f0/40090d148b8907fbea12e9bdf1ff149f30cdf1769e3b2c3e0dbf5106b88d/fonttools-4.60.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:55a3129d1e4030b1a30260f1b32fe76781b585fb2111d04a988e141c09eb6403", size = 4873503, upload-time = "2025-12-09T13:37:15.142Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/e0/d8b13f99e58b8c293781288ba62fe634f1f0697c9c4c0ae104d3215f3a10/fonttools-4.60.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b196e63753abc33b3b97a6fd6de4b7c4fef5552c0a5ba5e562be214d1e9668e0", size = 4968493, upload-time = "2025-12-09T13:37:18.272Z" },
+ { url = "https://files.pythonhosted.org/packages/46/c5/960764d12c92bc225f02401d3067048cb7b282293d9e48e39fe2b0ec38a9/fonttools-4.60.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de76c8d740fb55745f3b154f0470c56db92ae3be27af8ad6c2e88f1458260c9a", size = 4920015, upload-time = "2025-12-09T13:37:20.334Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/ab/839d8caf253d1eef3653ef4d34427d0326d17a53efaec9eb04056b670fff/fonttools-4.60.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ba6303225c95998c9fda2d410aa792c3d2c1390a09df58d194b03e17583fa25", size = 5031165, upload-time = "2025-12-09T13:37:23.57Z" },
+ { url = "https://files.pythonhosted.org/packages/de/bf/3bc862796a6841cbe0725bb5512d272239b809dba631a4b0301df885e62d/fonttools-4.60.2-cp314-cp314-win32.whl", hash = "sha256:0a89728ce10d7c816fedaa5380c06d2793e7a8a634d7ce16810e536c22047384", size = 2267526, upload-time = "2025-12-09T13:37:25.821Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/a1/c1909cacf00c76dc37b4743451561fbaaf7db4172c22a6d9394081d114c3/fonttools-4.60.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa8446e6ab8bd778b82cb1077058a2addba86f30de27ab9cc18ed32b34bc8667", size = 2319096, upload-time = "2025-12-09T13:37:28.058Z" },
+ { url = "https://files.pythonhosted.org/packages/29/b3/f66e71433f08e3a931b2b31a665aeed17fcc5e6911fc73529c70a232e421/fonttools-4.60.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4063bc81ac5a4137642865cb63dd270e37b3cd1f55a07c0d6e41d072699ccca2", size = 2925167, upload-time = "2025-12-09T13:37:30.348Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/13/eeb491ff743594bbd0bee6e49422c03a59fe9c49002d3cc60eeb77414285/fonttools-4.60.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebfdb66fa69732ed604ab8e2a0431e6deff35e933a11d73418cbc7823d03b8e1", size = 2430923, upload-time = "2025-12-09T13:37:32.817Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/e5/db609f785e460796e53c4dbc3874a5f4948477f27beceb5e2d24b2537666/fonttools-4.60.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50b10b3b1a72d1d54c61b0e59239e1a94c0958f4a06a1febf97ce75388dd91a4", size = 4877729, upload-time = "2025-12-09T13:37:35.858Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/d6/85e4484dd4bfb03fee7bd370d65888cccbd3dee2681ee48c869dd5ccb23f/fonttools-4.60.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:beae16891a13b4a2ddec9b39b4de76092a3025e4d1c82362e3042b62295d5e4d", size = 5096003, upload-time = "2025-12-09T13:37:37.862Z" },
+ { url = "https://files.pythonhosted.org/packages/30/49/1a98e44b71030b83d2046f981373b80571868259d98e6dae7bc20099dac6/fonttools-4.60.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:522f017fdb3766fd5d2d321774ef351cc6ce88ad4e6ac9efe643e4a2b9d528db", size = 4974410, upload-time = "2025-12-09T13:37:40.166Z" },
+ { url = "https://files.pythonhosted.org/packages/42/07/d6f775d950ee8a841012472c7303f8819423d8cc3b4530915de7265ebfa2/fonttools-4.60.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82cceceaf9c09a965a75b84a4b240dd3768e596ffb65ef53852681606fe7c9ba", size = 5002036, upload-time = "2025-12-09T13:37:42.639Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f6/ba6458f83ce1a9f8c3b17bd8f7b8a2205a126aac1055796b7e7cfebbd38f/fonttools-4.60.2-cp314-cp314t-win32.whl", hash = "sha256:bbfbc918a75437fe7e6d64d1b1e1f713237df1cf00f3a36dedae910b2ba01cee", size = 2330985, upload-time = "2025-12-09T13:37:45.157Z" },
+ { url = "https://files.pythonhosted.org/packages/91/24/fea0ba4d3a32d4ed1103a1098bfd99dc78b5fe3bb97202920744a37b73dc/fonttools-4.60.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0e5cd9b0830f6550d58c84f3ab151a9892b50c4f9d538c5603c0ce6fff2eb3f1", size = 2396226, upload-time = "2025-12-09T13:37:47.355Z" },
+ { url = "https://files.pythonhosted.org/packages/55/ae/a6d9446cb258d3fe87e311c2d7bacf8e8da3e5809fbdc3a8306db4f6b14e/fonttools-4.60.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c75b8b42f7f93906bdba9eb1197bb76aecbe9a0a7cf6feec75f7605b5e8008", size = 2857184, upload-time = "2025-12-09T13:37:49.96Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/f3/1b41d0b6a8b908aa07f652111155dd653ebbf0b3385e66562556c5206685/fonttools-4.60.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f86c8c37bc0ec0b9c141d5e90c717ff614e93c187f06d80f18c7057097f71bc", size = 2401877, upload-time = "2025-12-09T13:37:52.307Z" },
+ { url = "https://files.pythonhosted.org/packages/71/57/048fd781680c38b05c5463657d0d95d5f2391a51972176e175c01de29d42/fonttools-4.60.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe905403fe59683b0e9a45f234af2866834376b8821f34633b1c76fb731b6311", size = 4878073, upload-time = "2025-12-09T13:37:56.477Z" },
+ { url = "https://files.pythonhosted.org/packages/45/bb/363364f052a893cebd3d449588b21244a9d873620fda03ad92702d2e1bc7/fonttools-4.60.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38ce703b60a906e421e12d9e3a7f064883f5e61bb23e8961f4be33cfe578500b", size = 4835385, upload-time = "2025-12-09T13:37:58.882Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/38/e392bb930b2436287e6021672345db26441bf1f85f1e98f8b9784334e41d/fonttools-4.60.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9e810c06f3e79185cecf120e58b343ea5a89b54dd695fd644446bcf8c026da5e", size = 4853084, upload-time = "2025-12-09T13:38:01.578Z" },
+ { url = "https://files.pythonhosted.org/packages/65/60/0d77faeaecf7a3276a8a6dc49e2274357e6b3ed6a1774e2fdb2a7f142db0/fonttools-4.60.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:38faec8cc1d12122599814d15a402183f5123fb7608dac956121e7c6742aebc5", size = 4971144, upload-time = "2025-12-09T13:38:03.748Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/c7/6d3ac3afbcd598631bce24c3ecb919e7d0644a82fea8ddc4454312fc0be6/fonttools-4.60.2-cp39-cp39-win32.whl", hash = "sha256:80a45cf7bf659acb7b36578f300231873daba67bd3ca8cce181c73f861f14a37", size = 1499411, upload-time = "2025-12-09T13:38:05.586Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/1c/9dedf6420e23f9fa630bb97941839dddd2e1e57d1b2b85a902378dbe0bd2/fonttools-4.60.2-cp39-cp39-win_amd64.whl", hash = "sha256:c355d5972071938e1b1e0f5a1df001f68ecf1a62f34a3407dc8e0beccf052501", size = 1547943, upload-time = "2025-12-09T13:38:07.604Z" },
+ { url = "https://files.pythonhosted.org/packages/79/6c/10280af05b44fafd1dff69422805061fa1af29270bc52dce031ac69540bf/fonttools-4.60.2-py3-none-any.whl", hash = "sha256:73cf92eeda67cf6ff10c8af56fc8f4f07c1647d989a979be9e388a49be26552a", size = 1144610, upload-time = "2025-12-09T13:38:09.5Z" },
+]
+
+[[package]]
+name = "fonttools"
+version = "4.61.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" },
+ { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" },
+ { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" },
+ { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" },
+ { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" },
+ { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" },
+ { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" },
+ { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" },
+ { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" },
+ { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" },
+ { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" },
+ { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" },
+ { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" },
+ { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" },
+ { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" },
+ { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" },
+ { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" },
+ { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" },
+ { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" },
+ { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "imageio"
+version = "2.37.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" },
+]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
+]
+
+[[package]]
+name = "importlib-resources"
+version = "6.5.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.25.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version < '3.10'" },
+ { name = "jsonschema-specifications", marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "rpds-py", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.26.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version >= '3.10'" },
+ { name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "jupyter-client"
+version = "8.6.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version < '3.10'" },
+ { name = "pyzmq", marker = "python_full_version < '3.10'" },
+ { name = "tornado", marker = "python_full_version < '3.10'" },
+ { name = "traitlets", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" },
+]
+
+[[package]]
+name = "jupyter-client"
+version = "8.8.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version >= '3.10'" },
+ { name = "pyzmq", marker = "python_full_version >= '3.10'" },
+ { name = "tornado", marker = "python_full_version >= '3.10'" },
+ { name = "traitlets", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" },
+]
+
+[[package]]
+name = "jupyter-core"
+version = "5.8.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pywin32", marker = "python_full_version < '3.10' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'" },
+ { name = "traitlets", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" },
+]
+
+[[package]]
+name = "jupyter-core"
+version = "5.9.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "traitlets", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" },
+]
+
+[[package]]
+name = "jupyterlab-pygments"
+version = "0.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" },
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.7"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286, upload-time = "2024-09-04T09:39:44.302Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/97/14/fc943dd65268a96347472b4fbe5dcc2f6f55034516f80576cd0dd3a8930f/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6", size = 122440, upload-time = "2024-09-04T09:03:44.9Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/46/e68fed66236b69dd02fcdb506218c05ac0e39745d696d22709498896875d/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17", size = 65758, upload-time = "2024-09-04T09:03:46.582Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/fa/65de49c85838681fc9cb05de2a68067a683717321e01ddafb5b8024286f0/kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9", size = 64311, upload-time = "2024-09-04T09:03:47.973Z" },
+ { url = "https://files.pythonhosted.org/packages/42/9c/cc8d90f6ef550f65443bad5872ffa68f3dee36de4974768628bea7c14979/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9", size = 1637109, upload-time = "2024-09-04T09:03:49.281Z" },
+ { url = "https://files.pythonhosted.org/packages/55/91/0a57ce324caf2ff5403edab71c508dd8f648094b18cfbb4c8cc0fde4a6ac/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c", size = 1617814, upload-time = "2024-09-04T09:03:51.444Z" },
+ { url = "https://files.pythonhosted.org/packages/12/5d/c36140313f2510e20207708adf36ae4919416d697ee0236b0ddfb6fd1050/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599", size = 1400881, upload-time = "2024-09-04T09:03:53.357Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d0/786e524f9ed648324a466ca8df86298780ef2b29c25313d9a4f16992d3cf/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05", size = 1512972, upload-time = "2024-09-04T09:03:55.082Z" },
+ { url = "https://files.pythonhosted.org/packages/67/5a/77851f2f201e6141d63c10a0708e996a1363efaf9e1609ad0441b343763b/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407", size = 1444787, upload-time = "2024-09-04T09:03:56.588Z" },
+ { url = "https://files.pythonhosted.org/packages/06/5f/1f5eaab84355885e224a6fc8d73089e8713dc7e91c121f00b9a1c58a2195/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278", size = 2199212, upload-time = "2024-09-04T09:03:58.557Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/28/9152a3bfe976a0ae21d445415defc9d1cd8614b2910b7614b30b27a47270/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5", size = 2346399, upload-time = "2024-09-04T09:04:00.178Z" },
+ { url = "https://files.pythonhosted.org/packages/26/f6/453d1904c52ac3b400f4d5e240ac5fec25263716723e44be65f4d7149d13/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad", size = 2308688, upload-time = "2024-09-04T09:04:02.216Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/9a/d4968499441b9ae187e81745e3277a8b4d7c60840a52dc9d535a7909fac3/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895", size = 2445493, upload-time = "2024-09-04T09:04:04.571Z" },
+ { url = "https://files.pythonhosted.org/packages/07/c9/032267192e7828520dacb64dfdb1d74f292765f179e467c1cba97687f17d/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3", size = 2262191, upload-time = "2024-09-04T09:04:05.969Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644, upload-time = "2024-09-04T09:04:07.408Z" },
+ { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877, upload-time = "2024-09-04T09:04:08.869Z" },
+ { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347, upload-time = "2024-09-04T09:04:10.106Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442, upload-time = "2024-09-04T09:04:11.432Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762, upload-time = "2024-09-04T09:04:12.468Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319, upload-time = "2024-09-04T09:04:13.635Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/1b/b5d618f4e58c0675654c1e5051bcf42c776703edb21c02b8c74135541f60/kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb", size = 1334260, upload-time = "2024-09-04T09:04:14.878Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/01/946852b13057a162a8c32c4c8d2e9ed79f0bb5d86569a40c0b5fb103e373/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02", size = 1426589, upload-time = "2024-09-04T09:04:16.514Z" },
+ { url = "https://files.pythonhosted.org/packages/70/d1/c9f96df26b459e15cf8a965304e6e6f4eb291e0f7a9460b4ad97b047561e/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51", size = 1541080, upload-time = "2024-09-04T09:04:18.322Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/73/2686990eb8b02d05f3de759d6a23a4ee7d491e659007dd4c075fede4b5d0/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052", size = 1470049, upload-time = "2024-09-04T09:04:20.266Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/4b/2db7af3ed3af7c35f388d5f53c28e155cd402a55432d800c543dc6deb731/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18", size = 1426376, upload-time = "2024-09-04T09:04:22.419Z" },
+ { url = "https://files.pythonhosted.org/packages/05/83/2857317d04ea46dc5d115f0df7e676997bbd968ced8e2bd6f7f19cfc8d7f/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545", size = 2222231, upload-time = "2024-09-04T09:04:24.526Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/b5/866f86f5897cd4ab6d25d22e403404766a123f138bd6a02ecb2cdde52c18/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b", size = 2368634, upload-time = "2024-09-04T09:04:25.899Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ee/73de8385403faba55f782a41260210528fe3273d0cddcf6d51648202d6d0/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36", size = 2329024, upload-time = "2024-09-04T09:04:28.523Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/e7/cd101d8cd2cdfaa42dc06c433df17c8303d31129c9fdd16c0ea37672af91/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3", size = 2468484, upload-time = "2024-09-04T09:04:30.547Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/72/84f09d45a10bc57a40bb58b81b99d8f22b58b2040c912b7eb97ebf625bf2/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523", size = 2284078, upload-time = "2024-09-04T09:04:33.218Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645, upload-time = "2024-09-04T09:04:34.371Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022, upload-time = "2024-09-04T09:04:35.786Z" },
+ { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536, upload-time = "2024-09-04T09:04:37.525Z" },
+ { url = "https://files.pythonhosted.org/packages/97/9c/0a11c714cf8b6ef91001c8212c4ef207f772dd84540104952c45c1f0a249/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2", size = 121808, upload-time = "2024-09-04T09:04:38.637Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/d8/0fe8c5f5d35878ddd135f44f2af0e4e1d379e1c7b0716f97cdcb88d4fd27/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a", size = 65531, upload-time = "2024-09-04T09:04:39.694Z" },
+ { url = "https://files.pythonhosted.org/packages/80/c5/57fa58276dfdfa612241d640a64ca2f76adc6ffcebdbd135b4ef60095098/kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee", size = 63894, upload-time = "2024-09-04T09:04:41.6Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/e9/26d3edd4c4ad1c5b891d8747a4f81b1b0aba9fb9721de6600a4adc09773b/kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640", size = 1369296, upload-time = "2024-09-04T09:04:42.886Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/67/3f4850b5e6cffb75ec40577ddf54f7b82b15269cc5097ff2e968ee32ea7d/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f", size = 1461450, upload-time = "2024-09-04T09:04:46.284Z" },
+ { url = "https://files.pythonhosted.org/packages/52/be/86cbb9c9a315e98a8dc6b1d23c43cffd91d97d49318854f9c37b0e41cd68/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483", size = 1579168, upload-time = "2024-09-04T09:04:47.91Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/00/65061acf64bd5fd34c1f4ae53f20b43b0a017a541f242a60b135b9d1e301/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258", size = 1507308, upload-time = "2024-09-04T09:04:49.465Z" },
+ { url = "https://files.pythonhosted.org/packages/21/e4/c0b6746fd2eb62fe702118b3ca0cb384ce95e1261cfada58ff693aeec08a/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e", size = 1464186, upload-time = "2024-09-04T09:04:50.949Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/0f/529d0a9fffb4d514f2782c829b0b4b371f7f441d61aa55f1de1c614c4ef3/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107", size = 2247877, upload-time = "2024-09-04T09:04:52.388Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/e1/66603ad779258843036d45adcbe1af0d1a889a07af4635f8b4ec7dccda35/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948", size = 2404204, upload-time = "2024-09-04T09:04:54.385Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/61/de5fb1ca7ad1f9ab7970e340a5b833d735df24689047de6ae71ab9d8d0e7/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038", size = 2352461, upload-time = "2024-09-04T09:04:56.307Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/d2/0edc00a852e369827f7e05fd008275f550353f1f9bcd55db9363d779fc63/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383", size = 2501358, upload-time = "2024-09-04T09:04:57.922Z" },
+ { url = "https://files.pythonhosted.org/packages/84/15/adc15a483506aec6986c01fb7f237c3aec4d9ed4ac10b756e98a76835933/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520", size = 2314119, upload-time = "2024-09-04T09:04:59.332Z" },
+ { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367, upload-time = "2024-09-04T09:05:00.804Z" },
+ { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884, upload-time = "2024-09-04T09:05:01.924Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528, upload-time = "2024-09-04T09:05:02.983Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913, upload-time = "2024-09-04T09:05:04.072Z" },
+ { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627, upload-time = "2024-09-04T09:05:05.119Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888, upload-time = "2024-09-04T09:05:06.191Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145, upload-time = "2024-09-04T09:05:07.919Z" },
+ { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448, upload-time = "2024-09-04T09:05:10.01Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750, upload-time = "2024-09-04T09:05:11.598Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175, upload-time = "2024-09-04T09:05:13.22Z" },
+ { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963, upload-time = "2024-09-04T09:05:15.925Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220, upload-time = "2024-09-04T09:05:17.434Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463, upload-time = "2024-09-04T09:05:18.997Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842, upload-time = "2024-09-04T09:05:21.299Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635, upload-time = "2024-09-04T09:05:23.588Z" },
+ { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556, upload-time = "2024-09-04T09:05:25.907Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364, upload-time = "2024-09-04T09:05:27.184Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887, upload-time = "2024-09-04T09:05:28.372Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530, upload-time = "2024-09-04T09:05:30.225Z" },
+ { url = "https://files.pythonhosted.org/packages/11/88/37ea0ea64512997b13d69772db8dcdc3bfca5442cda3a5e4bb943652ee3e/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd", size = 122449, upload-time = "2024-09-04T09:05:55.311Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/45/5a5c46078362cb3882dcacad687c503089263c017ca1241e0483857791eb/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583", size = 65757, upload-time = "2024-09-04T09:05:56.906Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/be/a6ae58978772f685d48dd2e84460937761c53c4bbd84e42b0336473d9775/kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417", size = 64312, upload-time = "2024-09-04T09:05:58.384Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/04/18ef6f452d311e1e1eb180c9bf5589187fa1f042db877e6fe443ef10099c/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904", size = 1626966, upload-time = "2024-09-04T09:05:59.855Z" },
+ { url = "https://files.pythonhosted.org/packages/21/b1/40655f6c3fa11ce740e8a964fa8e4c0479c87d6a7944b95af799c7a55dfe/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a", size = 1607044, upload-time = "2024-09-04T09:06:02.16Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/93/af67dbcfb9b3323bbd2c2db1385a7139d8f77630e4a37bb945b57188eb2d/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8", size = 1391879, upload-time = "2024-09-04T09:06:03.908Z" },
+ { url = "https://files.pythonhosted.org/packages/40/6f/d60770ef98e77b365d96061d090c0cd9e23418121c55fff188fa4bdf0b54/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2", size = 1504751, upload-time = "2024-09-04T09:06:05.58Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/3a/5f38667d313e983c432f3fcd86932177519ed8790c724e07d77d1de0188a/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88", size = 1436990, upload-time = "2024-09-04T09:06:08.126Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/3b/1520301a47326e6a6043b502647e42892be33b3f051e9791cc8bb43f1a32/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde", size = 2191122, upload-time = "2024-09-04T09:06:10.345Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/c4/eb52da300c166239a2233f1f9c4a1b767dfab98fae27681bfb7ea4873cb6/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c", size = 2338126, upload-time = "2024-09-04T09:06:12.321Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/cb/42b92fd5eadd708dd9107c089e817945500685f3437ce1fd387efebc6d6e/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2", size = 2298313, upload-time = "2024-09-04T09:06:14.562Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/eb/be25aa791fe5fc75a8b1e0c965e00f942496bc04635c9aae8035f6b76dcd/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb", size = 2437784, upload-time = "2024-09-04T09:06:16.767Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/22/30a66be7f3368d76ff95689e1c2e28d382383952964ab15330a15d8bfd03/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327", size = 2253988, upload-time = "2024-09-04T09:06:18.705Z" },
+ { url = "https://files.pythonhosted.org/packages/35/d3/5f2ecb94b5211c8a04f218a76133cc8d6d153b0f9cd0b45fad79907f0689/kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644", size = 46980, upload-time = "2024-09-04T09:06:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/17/cd10d020578764ea91740204edc6b3236ed8106228a46f568d716b11feb2/kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4", size = 55847, upload-time = "2024-09-04T09:06:21.407Z" },
+ { url = "https://files.pythonhosted.org/packages/91/84/32232502020bd78d1d12be7afde15811c64a95ed1f606c10456db4e4c3ac/kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f", size = 48494, upload-time = "2024-09-04T09:06:22.648Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/59/741b79775d67ab67ced9bb38552da688c0305c16e7ee24bba7a2be253fb7/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643", size = 59491, upload-time = "2024-09-04T09:06:24.188Z" },
+ { url = "https://files.pythonhosted.org/packages/58/cc/fb239294c29a5656e99e3527f7369b174dd9cc7c3ef2dea7cb3c54a8737b/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706", size = 57648, upload-time = "2024-09-04T09:06:25.559Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/ef/2f009ac1f7aab9f81efb2d837301d255279d618d27b6015780115ac64bdd/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6", size = 84257, upload-time = "2024-09-04T09:06:27.038Z" },
+ { url = "https://files.pythonhosted.org/packages/81/e1/c64f50987f85b68b1c52b464bb5bf73e71570c0f7782d626d1eb283ad620/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2", size = 80906, upload-time = "2024-09-04T09:06:28.48Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/71/1687c5c0a0be2cee39a5c9c389e546f9c6e215e46b691d00d9f646892083/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4", size = 79951, upload-time = "2024-09-04T09:06:29.966Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715, upload-time = "2024-09-04T09:06:31.489Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/df/ce37d9b26f07ab90880923c94d12a6ff4d27447096b4c849bfc4339ccfdf/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39", size = 58666, upload-time = "2024-09-04T09:06:43.756Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/d3/e4b04f43bc629ac8e186b77b2b1a251cdfa5b7610fa189dc0db622672ce6/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e", size = 57088, upload-time = "2024-09-04T09:06:45.406Z" },
+ { url = "https://files.pythonhosted.org/packages/30/1c/752df58e2d339e670a535514d2db4fe8c842ce459776b8080fbe08ebb98e/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608", size = 84321, upload-time = "2024-09-04T09:06:47.557Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/f8/fe6484e847bc6e238ec9f9828089fb2c0bb53f2f5f3a79351fde5b565e4f/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674", size = 80776, upload-time = "2024-09-04T09:06:49.235Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/57/d7163c0379f250ef763aba85330a19feefb5ce6cb541ade853aaba881524/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225", size = 79984, upload-time = "2024-09-04T09:06:51.336Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811, upload-time = "2024-09-04T09:06:53.078Z" },
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.9"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" },
+ { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" },
+ { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" },
+ { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" },
+ { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" },
+ { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" },
+ { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" },
+ { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" },
+ { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" },
+ { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" },
+ { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" },
+ { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" },
+ { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" },
+ { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" },
+ { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" },
+ { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" },
+ { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" },
+ { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" },
+ { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" },
+ { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" },
+ { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" },
+ { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" },
+ { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" },
+ { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" },
+ { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" },
+ { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" },
+ { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" },
+ { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" },
+ { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" },
+ { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" },
+ { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" },
+ { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" },
+ { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" },
+ { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" },
+ { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" },
+ { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" },
+ { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" },
+ { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" },
+ { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" },
+ { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" },
+ { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" },
+ { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" },
+]
+
+[[package]]
+name = "latexcodec"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/27/dd/4270b2c5e2ee49316c3859e62293bd2ea8e382339d63ab7bbe9f39c0ec3b/latexcodec-3.0.1.tar.gz", hash = "sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357", size = 31222, upload-time = "2025-06-17T18:47:34.051Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b5/40/23569737873cc9637fd488606347e9dd92b9fa37ba4fcda1f98ee5219a97/latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e", size = 18532, upload-time = "2025-06-17T18:47:30.726Z" },
+]
+
+[[package]]
+name = "lazy-loader"
+version = "0.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" },
+ { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" },
+ { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" },
+ { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" },
+ { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" },
+ { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" },
+ { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
+ { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
+ { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
+ { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
+ { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
+ { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
+ { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
+ { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
+ { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
+ { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
+ { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
+ { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
+ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
+ { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" },
+ { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" },
+ { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" },
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.9.4"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "cycler", marker = "python_full_version < '3.10'" },
+ { name = "fonttools", version = "4.60.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "importlib-resources", marker = "python_full_version < '3.10'" },
+ { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pyparsing", marker = "python_full_version < '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/17/1747b4154034befd0ed33b52538f5eb7752d05bb51c5e2a31470c3bc7d52/matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3", size = 36106529, upload-time = "2024-12-13T05:56:34.184Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/94/27d2e2c30d54b56c7b764acc1874a909e34d1965a427fc7092bb6a588b63/matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50", size = 7885089, upload-time = "2024-12-13T05:54:24.224Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/25/828273307e40a68eb8e9df832b6b2aaad075864fdc1de4b1b81e40b09e48/matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff", size = 7770600, upload-time = "2024-12-13T05:54:27.214Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/65/f841a422ec994da5123368d76b126acf4fc02ea7459b6e37c4891b555b83/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26", size = 8200138, upload-time = "2024-12-13T05:54:29.497Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/272aca07a38804d93b6050813de41ca7ab0e29ba7a9dd098e12037c919a9/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50", size = 8312711, upload-time = "2024-12-13T05:54:34.396Z" },
+ { url = "https://files.pythonhosted.org/packages/98/37/f13e23b233c526b7e27ad61be0a771894a079e0f7494a10d8d81557e0e9a/matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5", size = 9090622, upload-time = "2024-12-13T05:54:36.808Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/8c/b1f5bd2bd70e60f93b1b54c4d5ba7a992312021d0ddddf572f9a1a6d9348/matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d", size = 7828211, upload-time = "2024-12-13T05:54:40.596Z" },
+ { url = "https://files.pythonhosted.org/packages/74/4b/65be7959a8fa118a3929b49a842de5b78bb55475236fcf64f3e308ff74a0/matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c", size = 7894430, upload-time = "2024-12-13T05:54:44.049Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/18/80f70d91896e0a517b4a051c3fd540daa131630fd75e02e250365353b253/matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099", size = 7780045, upload-time = "2024-12-13T05:54:46.414Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/73/ccb381026e3238c5c25c3609ba4157b2d1a617ec98d65a8b4ee4e1e74d02/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249", size = 8209906, upload-time = "2024-12-13T05:54:49.459Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/33/1648da77b74741c89f5ea95cbf42a291b4b364f2660b316318811404ed97/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423", size = 8322873, upload-time = "2024-12-13T05:54:53.066Z" },
+ { url = "https://files.pythonhosted.org/packages/57/d3/8447ba78bc6593c9044c372d1609f8ea10fb1e071e7a9e0747bea74fc16c/matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e", size = 9099566, upload-time = "2024-12-13T05:54:55.522Z" },
+ { url = "https://files.pythonhosted.org/packages/23/e1/4f0e237bf349c02ff9d1b6e7109f1a17f745263809b9714a8576dc17752b/matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3", size = 7838065, upload-time = "2024-12-13T05:54:58.337Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/2b/c918bf6c19d6445d1cefe3d2e42cb740fb997e14ab19d4daeb6a7ab8a157/matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70", size = 7891131, upload-time = "2024-12-13T05:55:02.837Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/e5/b4e8fc601ca302afeeabf45f30e706a445c7979a180e3a978b78b2b681a4/matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483", size = 7776365, upload-time = "2024-12-13T05:55:05.158Z" },
+ { url = "https://files.pythonhosted.org/packages/99/06/b991886c506506476e5d83625c5970c656a491b9f80161458fed94597808/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f", size = 8200707, upload-time = "2024-12-13T05:55:09.48Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/e2/556b627498cb27e61026f2d1ba86a78ad1b836fef0996bef5440e8bc9559/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00", size = 8313761, upload-time = "2024-12-13T05:55:12.95Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ff/165af33ec766ff818306ea88e91f9f60d2a6ed543be1eb122a98acbf3b0d/matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0", size = 9095284, upload-time = "2024-12-13T05:55:16.199Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/8b/3d0c7a002db3b1ed702731c2a9a06d78d035f1f2fb0fb936a8e43cc1e9f4/matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b", size = 7841160, upload-time = "2024-12-13T05:55:19.991Z" },
+ { url = "https://files.pythonhosted.org/packages/49/b1/999f89a7556d101b23a2f0b54f1b6e140d73f56804da1398f2f0bc0924bc/matplotlib-3.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37eeffeeca3c940985b80f5b9a7b95ea35671e0e7405001f249848d2b62351b6", size = 7891499, upload-time = "2024-12-13T05:55:22.142Z" },
+ { url = "https://files.pythonhosted.org/packages/87/7b/06a32b13a684977653396a1bfcd34d4e7539c5d55c8cbfaa8ae04d47e4a9/matplotlib-3.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e7465ac859ee4abcb0d836137cd8414e7bb7ad330d905abced457217d4f0f45", size = 7776802, upload-time = "2024-12-13T05:55:25.947Z" },
+ { url = "https://files.pythonhosted.org/packages/65/87/ac498451aff739e515891bbb92e566f3c7ef31891aaa878402a71f9b0910/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c12302c34afa0cf061bea23b331e747e5e554b0fa595c96e01c7b75bc3b858", size = 8200802, upload-time = "2024-12-13T05:55:28.461Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/6b/9eb761c00e1cb838f6c92e5f25dcda3f56a87a52f6cb8fdfa561e6cf6a13/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8c97917f21b75e72108b97707ba3d48f171541a74aa2a56df7a40626bafc64", size = 8313880, upload-time = "2024-12-13T05:55:30.965Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/a2/c8eaa600e2085eec7e38cbbcc58a30fc78f8224939d31d3152bdafc01fd1/matplotlib-3.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0229803bd7e19271b03cb09f27db76c918c467aa4ce2ae168171bc67c3f508df", size = 9094637, upload-time = "2024-12-13T05:55:33.701Z" },
+ { url = "https://files.pythonhosted.org/packages/71/1f/c6e1daea55b7bfeb3d84c6cb1abc449f6a02b181e7e2a5e4db34c3afb793/matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799", size = 7841311, upload-time = "2024-12-13T05:55:36.737Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/3a/2757d3f7d388b14dd48f5a83bea65b6d69f000e86b8f28f74d86e0d375bd/matplotlib-3.9.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a04c3b00066a688834356d196136349cb32f5e1003c55ac419e91585168b88fb", size = 7919989, upload-time = "2024-12-13T05:55:39.024Z" },
+ { url = "https://files.pythonhosted.org/packages/24/28/f5077c79a4f521589a37fe1062d6a6ea3534e068213f7357e7cfffc2e17a/matplotlib-3.9.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04c519587f6c210626741a1e9a68eefc05966ede24205db8982841826af5871a", size = 7809417, upload-time = "2024-12-13T05:55:42.412Z" },
+ { url = "https://files.pythonhosted.org/packages/36/c8/c523fd2963156692916a8eb7d4069084cf729359f7955cf09075deddfeaf/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308afbf1a228b8b525fcd5cec17f246bbbb63b175a3ef6eb7b4d33287ca0cf0c", size = 8226258, upload-time = "2024-12-13T05:55:47.259Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/88/499bf4b8fa9349b6f5c0cf4cead0ebe5da9d67769129f1b5651e5ac51fbc/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb3b02246ddcffd3ce98e88fed5b238bc5faff10dbbaa42090ea13241d15764", size = 8335849, upload-time = "2024-12-13T05:55:49.763Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/9f/20a4156b9726188646a030774ee337d5ff695a965be45ce4dbcb9312c170/matplotlib-3.9.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8a75287e9cb9eee48cb79ec1d806f75b29c0fde978cb7223a1f4c5848d696041", size = 9102152, upload-time = "2024-12-13T05:55:51.997Z" },
+ { url = "https://files.pythonhosted.org/packages/10/11/237f9c3a4e8d810b1759b67ff2da7c32c04f9c80aa475e7beb36ed43a8fb/matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965", size = 7896987, upload-time = "2024-12-13T05:55:55.941Z" },
+ { url = "https://files.pythonhosted.org/packages/56/eb/501b465c9fef28f158e414ea3a417913dc2ac748564c7ed41535f23445b4/matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c", size = 7885919, upload-time = "2024-12-13T05:55:59.66Z" },
+ { url = "https://files.pythonhosted.org/packages/da/36/236fbd868b6c91309a5206bd90c3f881f4f44b2d997cd1d6239ef652f878/matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7", size = 7771486, upload-time = "2024-12-13T05:56:04.264Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/4b/105caf2d54d5ed11d9f4335398f5103001a03515f2126c936a752ccf1461/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e", size = 8201838, upload-time = "2024-12-13T05:56:06.792Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/a7/bb01188fb4013d34d274caf44a2f8091255b0497438e8b6c0a7c1710c692/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c", size = 8314492, upload-time = "2024-12-13T05:56:09.964Z" },
+ { url = "https://files.pythonhosted.org/packages/33/19/02e1a37f7141fc605b193e927d0a9cdf9dc124a20b9e68793f4ffea19695/matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb", size = 9092500, upload-time = "2024-12-13T05:56:13.55Z" },
+ { url = "https://files.pythonhosted.org/packages/57/68/c2feb4667adbf882ffa4b3e0ac9967f848980d9f8b5bebd86644aa67ce6a/matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac", size = 7822962, upload-time = "2024-12-13T05:56:16.358Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/22/2ef6a364cd3f565442b0b055e0599744f1e4314ec7326cdaaa48a4d864d7/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c", size = 7877995, upload-time = "2024-12-13T05:56:18.805Z" },
+ { url = "https://files.pythonhosted.org/packages/87/b8/2737456e566e9f4d94ae76b8aa0d953d9acb847714f9a7ad80184474f5be/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca", size = 7769300, upload-time = "2024-12-13T05:56:21.315Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/1f/e709c6ec7b5321e6568769baa288c7178e60a93a9da9e682b39450da0e29/matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db", size = 8313423, upload-time = "2024-12-13T05:56:26.719Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/b6/5a1f868782cd13f053a679984e222007ecff654a9bfbac6b27a65f4eeb05/matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865", size = 7854624, upload-time = "2024-12-13T05:56:29.359Z" },
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.10.8"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "cycler", marker = "python_full_version >= '3.10'" },
+ { name = "fonttools", version = "4.61.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "kiwisolver", version = "1.4.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "packaging", marker = "python_full_version >= '3.10'" },
+ { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pyparsing", marker = "python_full_version >= '3.10'" },
+ { name = "python-dateutil", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" },
+ { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" },
+ { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" },
+ { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" },
+ { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" },
+ { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" },
+ { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" },
+ { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" },
+ { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" },
+ { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" },
+ { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" },
+ { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" },
+ { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" },
+ { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" },
+ { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" },
+ { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" },
+ { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" },
+]
+
+[[package]]
+name = "mistune"
+version = "3.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" },
+]
+
+[[package]]
+name = "mock"
+version = "5.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/07/8c/14c2ae915e5f9dca5a22edd68b35be94400719ccfa068a03e0fb63d0f6f6/mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0", size = 92796, upload-time = "2025-03-03T12:31:42.911Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bd/d9/617e6af809bf3a1d468e0d58c3997b1dc219a9a9202e650d30c2fc85d481/mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f", size = 31617, upload-time = "2025-03-03T12:31:41.518Z" },
+]
+
+[[package]]
+name = "natsort"
+version = "8.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" },
+]
+
+[[package]]
+name = "nbclient"
+version = "0.10.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "jupyter-client", version = "8.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "nbformat", marker = "python_full_version < '3.10'" },
+ { name = "traitlets", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" },
+]
+
+[[package]]
+name = "nbclient"
+version = "0.10.4"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "jupyter-client", version = "8.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "nbformat", marker = "python_full_version >= '3.10'" },
+ { name = "traitlets", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" },
+]
+
+[[package]]
+name = "nbconvert"
+version = "7.16.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "beautifulsoup4" },
+ { name = "bleach", version = "6.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version < '3.10'" },
+ { name = "bleach", version = "6.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["css"], marker = "python_full_version >= '3.10'" },
+ { name = "defusedxml" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jinja2" },
+ { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "jupyterlab-pygments" },
+ { name = "markupsafe" },
+ { name = "mistune" },
+ { name = "nbclient", version = "0.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "nbclient", version = "0.10.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "nbformat" },
+ { name = "packaging" },
+ { name = "pandocfilters" },
+ { name = "pygments" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" },
+]
+
+[[package]]
+name = "nbformat"
+version = "5.10.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "fastjsonschema" },
+ { name = "jsonschema", version = "4.25.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "jsonschema", version = "4.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "jupyter-core", version = "5.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "jupyter-core", version = "5.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" },
+]
+
+[[package]]
+name = "nbsphinx"
+version = "0.9.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "jinja2" },
+ { name = "nbconvert" },
+ { name = "nbformat" },
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e7/d1/82081750f8a78ad0399c6ed831d42623b891904e8e7b8a75878225cf1dce/nbsphinx-0.9.8.tar.gz", hash = "sha256:d0765908399a8ee2b57be7ae881cf2ea58d66db3af7bbf33e6eb48f83bea5495", size = 417469, upload-time = "2025-11-28T17:41:02.336Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/03/78/843bcf0cf31f88d2f8a9a063d2d80817b1901657d83d65b89b3aa835732e/nbsphinx-0.9.8-py3-none-any.whl", hash = "sha256:92d95ee91784e56bc633b60b767a6b6f23a0445f891e24641ce3c3f004759ccf", size = 31961, upload-time = "2025-11-28T17:41:00.796Z" },
+]
+
+[[package]]
+name = "networkx"
+version = "3.2.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928, upload-time = "2023-10-28T08:41:39.364Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772, upload-time = "2023-10-28T08:41:36.945Z" },
+]
+
+[[package]]
+name = "networkx"
+version = "3.4.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" },
+]
+
+[[package]]
+name = "networkx"
+version = "3.6.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.0.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" },
+ { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" },
+ { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" },
+ { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" },
+ { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" },
+ { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" },
+ { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" },
+ { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" },
+ { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" },
+ { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" },
+ { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" },
+ { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" },
+ { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" },
+ { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" },
+ { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.2.6"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
+ { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" },
+ { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" },
+ { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
+ { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
+ { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
+ { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
+ { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
+ { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
+ { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
+ { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
+ { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
+ { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
+ { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" },
+ { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" },
+ { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" },
+ { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" },
+ { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" },
+ { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" },
+ { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" },
+ { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" },
+ { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" },
+ { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" },
+ { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
+ { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
+ { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
+ { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/26/7e/7bae7cbcc2f8132271967aa03e03954fc1e48aa1f3bf32b29ca95fbef352/numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e", size = 16940166, upload-time = "2025-12-20T16:15:43.434Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/27/6c13f5b46776d6246ec884ac5817452672156a506d08a1f2abb39961930a/numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db", size = 12641781, upload-time = "2025-12-20T16:15:45.701Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1c/83b4998d4860d15283241d9e5215f28b40ac31f497c04b12fa7f428ff370/numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b", size = 5470247, upload-time = "2025-12-20T16:15:47.943Z" },
+ { url = "https://files.pythonhosted.org/packages/54/08/cbce72c835d937795571b0464b52069f869c9e78b0c076d416c5269d2718/numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7", size = 6799807, upload-time = "2025-12-20T16:15:49.795Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/be/2e647961cd8c980591d75cdcd9e8f647d69fbe05e2a25613dc0a2ea5fb1a/numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548", size = 14701992, upload-time = "2025-12-20T16:15:51.615Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/fb/e1652fb8b6fd91ce6ed429143fe2e01ce714711e03e5b762615e7b36172c/numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346", size = 16646871, upload-time = "2025-12-20T16:15:54.129Z" },
+ { url = "https://files.pythonhosted.org/packages/62/23/d841207e63c4322842f7cd042ae981cffe715c73376dcad8235fb31debf1/numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25", size = 16487190, upload-time = "2025-12-20T16:15:56.147Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/a0/6a842c8421ebfdec0a230e65f61e0dabda6edbef443d999d79b87c273965/numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a", size = 18580762, upload-time = "2025-12-20T16:15:58.524Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/d1/c79e0046641186f2134dde05e6181825b911f8bdcef31b19ddd16e232847/numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53", size = 6233359, upload-time = "2025-12-20T16:16:00.938Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/f0/74965001d231f28184d6305b8cdc1b6fcd4bf23033f6cb039cfe76c9fca7/numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479", size = 12601132, upload-time = "2025-12-20T16:16:02.484Z" },
+ { url = "https://files.pythonhosted.org/packages/65/32/55408d0f46dfebce38017f5bd931affa7256ad6beac1a92a012e1fbc67a7/numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6", size = 10573977, upload-time = "2025-12-20T16:16:04.77Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ff/f6400ffec95de41c74b8e73df32e3fff1830633193a7b1e409be7fb1bb8c/numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037", size = 16653117, upload-time = "2025-12-20T16:16:06.709Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/28/6c23e97450035072e8d830a3c411bf1abd1f42c611ff9d29e3d8f55c6252/numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83", size = 12369711, upload-time = "2025-12-20T16:16:08.758Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/af/acbef97b630ab1bb45e6a7d01d1452e4251aa88ce680ac36e56c272120ec/numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344", size = 5198355, upload-time = "2025-12-20T16:16:10.902Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/c8/4e0d436b66b826f2e53330adaa6311f5cac9871a5b5c31ad773b27f25a74/numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6", size = 6545298, upload-time = "2025-12-20T16:16:12.607Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/27/e1f5d144ab54eac34875e79037011d511ac57b21b220063310cb96c80fbc/numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb", size = 14398387, upload-time = "2025-12-20T16:16:14.257Z" },
+ { url = "https://files.pythonhosted.org/packages/67/64/4cb909dd5ab09a9a5d086eff9586e69e827b88a5585517386879474f4cf7/numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63", size = 16363091, upload-time = "2025-12-20T16:16:17.32Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/9c/8efe24577523ec6809261859737cf117b0eb6fdb655abdfdc81b2e468ce4/numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95", size = 16176394, upload-time = "2025-12-20T16:16:19.524Z" },
+ { url = "https://files.pythonhosted.org/packages/61/f0/1687441ece7b47a62e45a1f82015352c240765c707928edd8aef875d5951/numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6", size = 18287378, upload-time = "2025-12-20T16:16:22.866Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/6f/f868765d44e6fc466467ed810ba9d8d6db1add7d4a748abfa2a4c99a3194/numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c", size = 5955432, upload-time = "2025-12-20T16:16:25.06Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/b5/94c1e79fcbab38d1ca15e13777477b2914dd2d559b410f96949d6637b085/numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98", size = 12306201, upload-time = "2025-12-20T16:16:26.979Z" },
+ { url = "https://files.pythonhosted.org/packages/70/09/c39dadf0b13bb0768cd29d6a3aaff1fb7c6905ac40e9aaeca26b1c086e06/numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667", size = 10308234, upload-time = "2025-12-20T16:16:29.417Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/0d/853fd96372eda07c824d24adf02e8bc92bb3731b43a9b2a39161c3667cc4/numpy-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a152d86a3ae00ba5f47b3acf3b827509fd0b6cb7d3259665e63dafbad22a75ea", size = 16649088, upload-time = "2025-12-20T16:16:31.421Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/37/cc636f1f2a9f585434e20a3e6e63422f70bfe4f7f6698e941db52ea1ac9a/numpy-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39b19251dec4de8ff8496cd0806cbe27bf0684f765abb1f4809554de93785f2d", size = 12364065, upload-time = "2025-12-20T16:16:33.491Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/69/0b78f37ca3690969beee54103ce5f6021709134e8020767e93ba691a72f1/numpy-2.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:009bd0ea12d3c784b6639a8457537016ce5172109e585338e11334f6a7bb88ee", size = 5192640, upload-time = "2025-12-20T16:16:35.636Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/2a/08569f8252abf590294dbb09a430543ec8f8cc710383abfb3e75cc73aeda/numpy-2.4.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5fe44e277225fd3dff6882d86d3d447205d43532c3627313d17e754fb3905a0e", size = 6541556, upload-time = "2025-12-20T16:16:37.276Z" },
+ { url = "https://files.pythonhosted.org/packages/93/e9/a949885a4e177493d61519377952186b6cbfdf1d6002764c664ba28349b5/numpy-2.4.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f935c4493eda9069851058fa0d9e39dbf6286be690066509305e52912714dbb2", size = 14396562, upload-time = "2025-12-20T16:16:38.953Z" },
+ { url = "https://files.pythonhosted.org/packages/99/98/9d4ad53b0e9ef901c2ef1d550d2136f5ac42d3fd2988390a6def32e23e48/numpy-2.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cfa5f29a695cb7438965e6c3e8d06e0416060cf0d709c1b1c1653a939bf5c2a", size = 16351719, upload-time = "2025-12-20T16:16:41.503Z" },
+ { url = "https://files.pythonhosted.org/packages/28/de/5f3711a38341d6e8dd619f6353251a0cdd07f3d6d101a8fd46f4ef87f895/numpy-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba0cb30acd3ef11c94dc27fbfba68940652492bc107075e7ffe23057f9425681", size = 16176053, upload-time = "2025-12-20T16:16:44.552Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/5b/2a3753dc43916501b4183532e7ace862e13211042bceafa253afb5c71272/numpy-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60e8c196cd82cbbd4f130b5290007e13e6de3eca79f0d4d38014769d96a7c475", size = 18277859, upload-time = "2025-12-20T16:16:47.174Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/c5/a18bcdd07a941db3076ef489d036ab16d2bfc2eae0cf27e5a26e29189434/numpy-2.4.0-cp313-cp313-win32.whl", hash = "sha256:5f48cb3e88fbc294dc90e215d86fbaf1c852c63dbdb6c3a3e63f45c4b57f7344", size = 5953849, upload-time = "2025-12-20T16:16:49.554Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/f1/719010ff8061da6e8a26e1980cf090412d4f5f8060b31f0c45d77dd67a01/numpy-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:a899699294f28f7be8992853c0c60741f16ff199205e2e6cdca155762cbaa59d", size = 12302840, upload-time = "2025-12-20T16:16:51.227Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/5a/b3d259083ed8b4d335270c76966cb6cf14a5d1b69e1a608994ac57a659e6/numpy-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9198f447e1dc5647d07c9a6bbe2063cc0132728cc7175b39dbc796da5b54920d", size = 10308509, upload-time = "2025-12-20T16:16:53.313Z" },
+ { url = "https://files.pythonhosted.org/packages/31/01/95edcffd1bb6c0633df4e808130545c4f07383ab629ac7e316fb44fff677/numpy-2.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74623f2ab5cc3f7c886add4f735d1031a1d2be4a4ae63c0546cfd74e7a31ddf6", size = 12491815, upload-time = "2025-12-20T16:16:55.496Z" },
+ { url = "https://files.pythonhosted.org/packages/59/ea/5644b8baa92cc1c7163b4b4458c8679852733fa74ca49c942cfa82ded4e0/numpy-2.4.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0804a8e4ab070d1d35496e65ffd3cf8114c136a2b81f61dfab0de4b218aacfd5", size = 5320321, upload-time = "2025-12-20T16:16:57.468Z" },
+ { url = "https://files.pythonhosted.org/packages/26/4e/e10938106d70bc21319bd6a86ae726da37edc802ce35a3a71ecdf1fdfe7f/numpy-2.4.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:02a2038eb27f9443a8b266a66911e926566b5a6ffd1a689b588f7f35b81e7dc3", size = 6641635, upload-time = "2025-12-20T16:16:59.379Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/8d/a8828e3eaf5c0b4ab116924df82f24ce3416fa38d0674d8f708ddc6c8aac/numpy-2.4.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1889b3a3f47a7b5bee16bc25a2145bd7cb91897f815ce3499db64c7458b6d91d", size = 14456053, upload-time = "2025-12-20T16:17:01.768Z" },
+ { url = "https://files.pythonhosted.org/packages/68/a1/17d97609d87d4520aa5ae2dcfb32305654550ac6a35effb946d303e594ce/numpy-2.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85eef4cb5625c47ee6425c58a3502555e10f45ee973da878ac8248ad58c136f3", size = 16401702, upload-time = "2025-12-20T16:17:04.235Z" },
+ { url = "https://files.pythonhosted.org/packages/18/32/0f13c1b2d22bea1118356b8b963195446f3af124ed7a5adfa8fdecb1b6ca/numpy-2.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6dc8b7e2f4eb184b37655195f421836cfae6f58197b67e3ffc501f1333d993fa", size = 16242493, upload-time = "2025-12-20T16:17:06.856Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/23/48f21e3d309fbc137c068a1475358cbd3a901b3987dcfc97a029ab3068e2/numpy-2.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:44aba2f0cafd287871a495fb3163408b0bd25bbce135c6f621534a07f4f7875c", size = 18324222, upload-time = "2025-12-20T16:17:09.392Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/52/41f3d71296a3dcaa4f456aaa3c6fc8e745b43d0552b6bde56571bb4b4a0f/numpy-2.4.0-cp313-cp313t-win32.whl", hash = "sha256:20c115517513831860c573996e395707aa9fb691eb179200125c250e895fcd93", size = 6076216, upload-time = "2025-12-20T16:17:11.437Z" },
+ { url = "https://files.pythonhosted.org/packages/35/ff/46fbfe60ab0710d2a2b16995f708750307d30eccbb4c38371ea9e986866e/numpy-2.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b48e35f4ab6f6a7597c46e301126ceba4c44cd3280e3750f85db48b082624fa4", size = 12444263, upload-time = "2025-12-20T16:17:13.182Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/e3/9189ab319c01d2ed556c932ccf55064c5d75bb5850d1df7a482ce0badead/numpy-2.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4d1cfce39e511069b11e67cd0bd78ceff31443b7c9e5c04db73c7a19f572967c", size = 10378265, upload-time = "2025-12-20T16:17:15.211Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/ed/52eac27de39d5e5a6c9aadabe672bc06f55e24a3d9010cd1183948055d76/numpy-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c95eb6db2884917d86cde0b4d4cf31adf485c8ec36bf8696dd66fa70de96f36b", size = 16647476, upload-time = "2025-12-20T16:17:17.671Z" },
+ { url = "https://files.pythonhosted.org/packages/77/c0/990ce1b7fcd4e09aeaa574e2a0a839589e4b08b2ca68070f1acb1fea6736/numpy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:65167da969cd1ec3a1df31cb221ca3a19a8aaa25370ecb17d428415e93c1935e", size = 12374563, upload-time = "2025-12-20T16:17:20.216Z" },
+ { url = "https://files.pythonhosted.org/packages/37/7c/8c5e389c6ae8f5fd2277a988600d79e9625db3fff011a2d87ac80b881a4c/numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3de19cfecd1465d0dcf8a5b5ea8b3155b42ed0b639dba4b71e323d74f2a3be5e", size = 5203107, upload-time = "2025-12-20T16:17:22.47Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/94/ca5b3bd6a8a70a5eec9a0b8dd7f980c1eff4b8a54970a9a7fef248ef564f/numpy-2.4.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6c05483c3136ac4c91b4e81903cb53a8707d316f488124d0398499a4f8e8ef51", size = 6538067, upload-time = "2025-12-20T16:17:24.001Z" },
+ { url = "https://files.pythonhosted.org/packages/79/43/993eb7bb5be6761dde2b3a3a594d689cec83398e3f58f4758010f3b85727/numpy-2.4.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36667db4d6c1cea79c8930ab72fadfb4060feb4bfe724141cd4bd064d2e5f8ce", size = 14411926, upload-time = "2025-12-20T16:17:25.822Z" },
+ { url = "https://files.pythonhosted.org/packages/03/75/d4c43b61de473912496317a854dac54f1efec3eeb158438da6884b70bb90/numpy-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a818668b674047fd88c4cddada7ab8f1c298812783e8328e956b78dc4807f9f", size = 16354295, upload-time = "2025-12-20T16:17:28.308Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/0a/b54615b47ee8736a6461a4bb6749128dd3435c5a759d5663f11f0e9af4ac/numpy-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ee32359fb7543b7b7bd0b2f46294db27e29e7bbdf70541e81b190836cd83ded", size = 16190242, upload-time = "2025-12-20T16:17:30.993Z" },
+ { url = "https://files.pythonhosted.org/packages/98/ce/ea207769aacad6246525ec6c6bbd66a2bf56c72443dc10e2f90feed29290/numpy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e493962256a38f58283de033d8af176c5c91c084ea30f15834f7545451c42059", size = 18280875, upload-time = "2025-12-20T16:17:33.327Z" },
+ { url = "https://files.pythonhosted.org/packages/17/ef/ec409437aa962ea372ed601c519a2b141701683ff028f894b7466f0ab42b/numpy-2.4.0-cp314-cp314-win32.whl", hash = "sha256:6bbaebf0d11567fa8926215ae731e1d58e6ec28a8a25235b8a47405d301332db", size = 6002530, upload-time = "2025-12-20T16:17:35.729Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/4a/5cb94c787a3ed1ac65e1271b968686521169a7b3ec0b6544bb3ca32960b0/numpy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d857f55e7fdf7c38ab96c4558c95b97d1c685be6b05c249f5fdafcbd6f9899e", size = 12435890, upload-time = "2025-12-20T16:17:37.599Z" },
+ { url = "https://files.pythonhosted.org/packages/48/a0/04b89db963af9de1104975e2544f30de89adbf75b9e75f7dd2599be12c79/numpy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:bb50ce5fb202a26fd5404620e7ef820ad1ab3558b444cb0b55beb7ef66cd2d63", size = 10591892, upload-time = "2025-12-20T16:17:39.649Z" },
+ { url = "https://files.pythonhosted.org/packages/53/e5/d74b5ccf6712c06c7a545025a6a71bfa03bdc7e0568b405b0d655232fd92/numpy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:355354388cba60f2132df297e2d53053d4063f79077b67b481d21276d61fc4df", size = 12494312, upload-time = "2025-12-20T16:17:41.714Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/08/3ca9cc2ddf54dfee7ae9a6479c071092a228c68aef08252aa08dac2af002/numpy-2.4.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1d8f9fde5f6dc1b6fc34df8162f3b3079365468703fee7f31d4e0cc8c63baed9", size = 5322862, upload-time = "2025-12-20T16:17:44.145Z" },
+ { url = "https://files.pythonhosted.org/packages/87/74/0bb63a68394c0c1e52670cfff2e309afa41edbe11b3327d9af29e4383f34/numpy-2.4.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e0434aa22c821f44eeb4c650b81c7fbdd8c0122c6c4b5a576a76d5a35625ecd9", size = 6644986, upload-time = "2025-12-20T16:17:46.203Z" },
+ { url = "https://files.pythonhosted.org/packages/06/8f/9264d9bdbcf8236af2823623fe2f3981d740fc3461e2787e231d97c38c28/numpy-2.4.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40483b2f2d3ba7aad426443767ff5632ec3156ef09742b96913787d13c336471", size = 14457958, upload-time = "2025-12-20T16:17:48.017Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/d9/f9a69ae564bbc7236a35aa883319364ef5fd41f72aa320cc1cbe66148fe2/numpy-2.4.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6a7664ddd9746e20b7325351fe1a8408d0a2bf9c63b5e898290ddc8f09544", size = 16398394, upload-time = "2025-12-20T16:17:50.409Z" },
+ { url = "https://files.pythonhosted.org/packages/34/c7/39241501408dde7f885d241a98caba5421061a2c6d2b2197ac5e3aa842d8/numpy-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ecb0019d44f4cdb50b676c5d0cb4b1eae8e15d1ed3d3e6639f986fc92b2ec52c", size = 16241044, upload-time = "2025-12-20T16:17:52.661Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/95/cae7effd90e065a95e59fe710eeee05d7328ed169776dfdd9f789e032125/numpy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d0ffd9e2e4441c96a9c91ec1783285d80bf835b677853fc2770a89d50c1e48ac", size = 18321772, upload-time = "2025-12-20T16:17:54.947Z" },
+ { url = "https://files.pythonhosted.org/packages/96/df/3c6c279accd2bfb968a76298e5b276310bd55d243df4fa8ac5816d79347d/numpy-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:77f0d13fa87036d7553bf81f0e1fe3ce68d14c9976c9851744e4d3e91127e95f", size = 6148320, upload-time = "2025-12-20T16:17:57.249Z" },
+ { url = "https://files.pythonhosted.org/packages/92/8d/f23033cce252e7a75cae853d17f582e86534c46404dea1c8ee094a9d6d84/numpy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b1f5b45829ac1848893f0ddf5cb326110604d6df96cdc255b0bf9edd154104d4", size = 12623460, upload-time = "2025-12-20T16:17:58.963Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/4f/1f8475907d1a7c4ef9020edf7f39ea2422ec896849245f00688e4b268a71/numpy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:23a3e9d1a6f360267e8fbb38ba5db355a6a7e9be71d7fce7ab3125e88bb646c8", size = 10661799, upload-time = "2025-12-20T16:18:01.078Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/ef/088e7c7342f300aaf3ee5f2c821c4b9996a1bef2aaf6a49cc8ab4883758e/numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6", size = 16819003, upload-time = "2025-12-20T16:18:03.41Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/ce/a53017b5443b4b84517182d463fc7bcc2adb4faa8b20813f8e5f5aeb5faa/numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4", size = 12567105, upload-time = "2025-12-20T16:18:05.594Z" },
+ { url = "https://files.pythonhosted.org/packages/77/58/5ff91b161f2ec650c88a626c3905d938c89aaadabd0431e6d9c1330c83e2/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba", size = 5395590, upload-time = "2025-12-20T16:18:08.031Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/4e/f1a084106df8c2df8132fc437e56987308e0524836aa7733721c8429d4fe/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d", size = 6709947, upload-time = "2025-12-20T16:18:09.836Z" },
+ { url = "https://files.pythonhosted.org/packages/63/09/3d8aeb809c0332c3f642da812ac2e3d74fc9252b3021f8c30c82e99e3f3d/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf", size = 14535119, upload-time = "2025-12-20T16:18:12.105Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/7f/68f0fc43a2cbdc6bb239160c754d87c922f60fbaa0fa3cd3d312b8a7f5ee/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98", size = 16475815, upload-time = "2025-12-20T16:18:14.433Z" },
+ { url = "https://files.pythonhosted.org/packages/11/73/edeacba3167b1ca66d51b1a5a14697c2c40098b5ffa01811c67b1785a5ab/numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b", size = 12489376, upload-time = "2025-12-20T16:18:16.524Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.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" }
+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" },
+]
+
+[[package]]
+name = "pandocfilters"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "11.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" },
+ { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" },
+ { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" },
+ { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" },
+ { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" },
+ { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" },
+ { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" },
+ { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" },
+ { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" },
+ { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" },
+ { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" },
+ { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" },
+ { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" },
+ { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" },
+ { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" },
+ { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" },
+ { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" },
+ { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" },
+ { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" },
+ { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" },
+ { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" },
+ { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" },
+ { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" },
+ { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" },
+ { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" },
+ { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" },
+ { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" },
+ { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" },
+ { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" },
+ { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" },
+ { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" },
+ { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" },
+ { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" },
+ { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" },
+ { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "12.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" },
+ { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" },
+ { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" },
+ { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" },
+ { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" },
+ { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" },
+ { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" },
+ { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" },
+ { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" },
+ { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" },
+ { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" },
+ { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" },
+ { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" },
+ { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" },
+ { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" },
+ { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" },
+ { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" },
+ { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" },
+ { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" },
+ { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" },
+ { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" },
+ { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" },
+ { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" },
+ { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" },
+ { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" },
+ { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" },
+ { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" },
+ { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.4.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pybtex"
+version = "0.25.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "latexcodec" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5f/bc/c2be05ca72f8c103670e983df8be26d1e288bc6556f487fa8cccaa27779f/pybtex-0.25.1.tar.gz", hash = "sha256:9eaf90267c7e83e225af89fea65c370afbf65f458220d3946a9e3049e1eca491", size = 406157, upload-time = "2025-06-26T13:27:41.903Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/25/68/ceb5d6679baa326261f5d3e5113d9cfed6efef2810afd9f18bffb8ed312b/pybtex-0.25.1-py2.py3-none-any.whl", hash = "sha256:9053b0d619409a0a83f38abad5d9921de5f7b3ede00742beafcd9f10ad0d8c5c", size = 127437, upload-time = "2025-06-26T13:27:43.585Z" },
+]
+
+[[package]]
+name = "pybtex-docutils"
+version = "1.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "pybtex" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7e/84/796ea94d26188a853660f81bded39f8de4cfe595130aef0dea1088705a11/pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b", size = 18348, upload-time = "2023-08-22T18:47:54.833Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385, upload-time = "2023-08-22T06:43:20.513Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "2.23"
+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" }
+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" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pylustrator"
+version = "1.3.0"
+source = { editable = "." }
+dependencies = [
+ { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "natsort" },
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "pyqt5" },
+ { name = "qtawesome" },
+ { name = "qtpy" },
+ { name = "scikit-image", version = "0.24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "ruff" },
+ { name = "ty" },
+]
+test = [
+ { name = "pytest" },
+]
+
+[package.dev-dependencies]
+docs = [
+ { name = "mock" },
+ { name = "nbsphinx" },
+ { name = "sphinx-rtd-theme" },
+ { name = "sphinxcontrib-bibtex" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "matplotlib", specifier = ">=2.0.2" },
+ { name = "natsort", specifier = ">=2.0.0" },
+ { name = "numpy", specifier = ">=1.0.3" },
+ { name = "pyqt5", specifier = ">=5.6" },
+ { name = "pytest", marker = "extra == 'test'", specifier = ">=7.2.0,<8.0.0" },
+ { name = "qtawesome", specifier = ">=0.5.0" },
+ { name = "qtpy", specifier = ">=2.4.3" },
+ { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.11" },
+ { name = "scikit-image", specifier = ">=0.7.0" },
+ { name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.10" },
+]
+provides-extras = ["dev", "test"]
+
+[package.metadata.requires-dev]
+docs = [
+ { name = "mock", specifier = ">=5.2.0" },
+ { name = "nbsphinx", specifier = ">=0.9.8" },
+ { name = "sphinx-rtd-theme", specifier = ">=3.0.2" },
+ { name = "sphinxcontrib-bibtex", specifier = ">=2.6.5" },
+]
+
+[[package]]
+name = "pyparsing"
+version = "3.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" },
+]
+
+[[package]]
+name = "pyqt5"
+version = "5.15.11"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyqt5-qt5" },
+ { name = "pyqt5-sip", version = "12.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pyqt5-sip", version = "12.17.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/07/c9ed0bd428df6f87183fca565a79fee19fa7c88c7f00a7f011ab4379e77a/PyQt5-5.15.11.tar.gz", hash = "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52", size = 3216775, upload-time = "2024-07-19T08:39:57.756Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/64/42ec1b0bd72d87f87bde6ceb6869f444d91a2d601f2e67cd05febc0346a1/PyQt5-5.15.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2", size = 6579776, upload-time = "2024-07-19T08:39:19.775Z" },
+ { url = "https://files.pythonhosted.org/packages/49/f5/3fb696f4683ea45d68b7e77302eff173493ac81e43d63adb60fa760b9f91/PyQt5-5.15.11-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6", size = 7016415, upload-time = "2024-07-19T08:39:32.977Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/8c/4065950f9d013c4b2e588fe33cf04e564c2322842d84dbcbce5ba1dc28b0/PyQt5-5.15.11-cp38-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1", size = 8188103, upload-time = "2024-07-19T08:39:40.561Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f0/ae5a5b4f9b826b29ea4be841b2f2d951bcf5ae1d802f3732b145b57c5355/PyQt5-5.15.11-cp38-abi3-win32.whl", hash = "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd", size = 5433308, upload-time = "2024-07-19T08:39:46.932Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d5/68eb9f3d19ce65df01b6c7b7a577ad3bbc9ab3a5dd3491a4756e71838ec9/PyQt5-5.15.11-cp38-abi3-win_amd64.whl", hash = "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517", size = 6865864, upload-time = "2024-07-19T08:39:53.572Z" },
+]
+
+[[package]]
+name = "pyqt5-qt5"
+version = "5.15.18"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/46/90/bf01ac2132400997a3474051dd680a583381ebf98b2f5d64d4e54138dc42/pyqt5_qt5-5.15.18-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:8bb997eb903afa9da3221a0c9e6eaa00413bbeb4394d5706118ad05375684767", size = 39715743, upload-time = "2025-11-09T12:56:42.936Z" },
+ { url = "https://files.pythonhosted.org/packages/24/8e/76366484d9f9dbe28e3bdfc688183433a7b82e314216e9b14c89e5fab690/pyqt5_qt5-5.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c656af9c1e6aaa7f59bf3d8995f2fa09adbf6762b470ed284c31dca80d686a26", size = 36798484, upload-time = "2025-11-09T12:56:59.998Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/46/ffe177f99f897a59dc237a20059020427bd2d3853d713992b8081933ddfe/pyqt5_qt5-5.15.18-py3-none-manylinux2014_x86_64.whl", hash = "sha256:bf2457e6371969736b4f660a0c153258fa03dbc6a181348218e6f05421682af7", size = 60864590, upload-time = "2025-11-09T12:57:26.724Z" },
+]
+
+[[package]]
+name = "pyqt5-sip"
+version = "12.17.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ea/08/88a20c862f40b5c178c517cdc7e93767967dec5ac1b994e226d517991c9b/pyqt5_sip-12.17.1.tar.gz", hash = "sha256:0eab72bcb628f1926bf5b9ac51259d4fa18e8b2a81d199071135458f7d087ea8", size = 104136, upload-time = "2025-10-08T09:04:19.893Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8f/38/d03cef62cc03348249ce279c7e42159d1c902b1d550924403b1986a2b0f4/pyqt5_sip-12.17.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bd4f73b1ebd5e0bd8d4539a8e55132318efc70a92f648ef0f9d93329ad50adeb", size = 122532, upload-time = "2025-10-08T09:04:12.141Z" },
+ { url = "https://files.pythonhosted.org/packages/08/92/5aa38d8c17ee857fc3f7866dc84d4f4e7ab2180b5026e4f6ffd594ed2432/pyqt5_sip-12.17.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b52e85520dbfe5c3d0c0c47aa2c10fc1853d892ae60ebebfe8154b052394da50", size = 271388, upload-time = "2025-10-08T09:15:32.369Z" },
+ { url = "https://files.pythonhosted.org/packages/58/5c/0990f3a9a07346417a6728102cbe3d97b13786bce974b24a8d649a49db59/pyqt5_sip-12.17.1-cp310-cp310-win32.whl", hash = "sha256:71a67e2c9b77a74e943e220db0a341c702fd9bcf83c4a2e07342dfce691742ae", size = 49092, upload-time = "2025-10-08T09:11:22.875Z" },
+ { url = "https://files.pythonhosted.org/packages/07/ad/f101338acf81cbd562362741aee9d0ee3c9242a6127c12ca698a15c851c6/pyqt5_sip-12.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:2710effb921bf6955b902779c763d890bb593da6325f0e128a0e3991cc855e9f", size = 58989, upload-time = "2025-10-08T09:08:33.535Z" },
+ { url = "https://files.pythonhosted.org/packages/15/e4/451e465c75584a7cbd10e10404317b7443af83f56a64e02080b1f3cda5b5/pyqt5_sip-12.17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5134d637efadd108a70306bab55b3d7feaa951bf6b8162161a67ae847bea9130", size = 122581, upload-time = "2025-10-08T09:04:13.607Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/b2/330f97434b21fbc99ab16f6ce71358ff5ea1bf1f09ed14dfe6b28b5ed8f5/pyqt5_sip-12.17.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:155cf755266c8bf64428916e2ff720d5efa1aec003d4ccc40c003b147dbdac03", size = 276844, upload-time = "2025-10-08T09:15:33.713Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/fd/53925099d0fc8aaf7adee613b6cebfb3fdfcd1238add64ff9edf6711e5f8/pyqt5_sip-12.17.1-cp311-cp311-win32.whl", hash = "sha256:9dfa7fe4ac93b60004430699c4bf56fef842a356d64dfea7cbc6d580d0427d6d", size = 49099, upload-time = "2025-10-08T09:11:23.928Z" },
+ { url = "https://files.pythonhosted.org/packages/33/f8/f47a849c17676557c4220fbce9fcc24e15736af247c4dddcaf9ff0124b57/pyqt5_sip-12.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ddd214cf40119b86942a5da2da5a7345334955ab00026d8dcc56326b30e6d3c", size = 58988, upload-time = "2025-10-08T09:08:34.903Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/15/291f83f336558300626bebb0c403084ec171bbc8a70683e3376234422eb6/pyqt5_sip-12.17.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c362606de782d2d46374a38523632786f145c517ee62de246a6069e5f2c5f336", size = 124521, upload-time = "2025-10-08T09:04:15.825Z" },
+ { url = "https://files.pythonhosted.org/packages/45/85/ea1ae099260fd1859d71b31f51760b4226abfa778d5796b76d92c8fe6dcd/pyqt5_sip-12.17.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:140cc582151456103ebb149fefc678f3cae803e7720733db51212af5219cd45c", size = 282182, upload-time = "2025-10-08T09:15:35.752Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/b3/d5b50c721651a0f2ccbef6f8db3dabf3db296b9ec239ba007f5615f57dd7/pyqt5_sip-12.17.1-cp312-cp312-win32.whl", hash = "sha256:9dc1f1525d4d42c080f6cfdfc70d78239f8f67b0a48ea0745497251d8d848b1d", size = 49447, upload-time = "2025-10-08T09:11:24.843Z" },
+ { url = "https://files.pythonhosted.org/packages/14/b6/474d8b17763683ab45fb364f3a44f25fdc25d97b47b29ad8819b95a15ac8/pyqt5_sip-12.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:d5e2e9e175559017cd161d661e0ee0b551684f824bb90800c5a8c8a3bea9355e", size = 57946, upload-time = "2025-10-08T09:08:35.775Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/58/9ecb688050e79ffe7bbd9fc917aa13f63856a5081ac46bbce87bb11ab971/pyqt5_sip-12.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9ebbd7769ccdaaa6295e9c872553b6cde17f38e171056f17300d8af9a14d1fc8", size = 124485, upload-time = "2025-10-08T09:04:17.473Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/9f/ae691360a9f18e3e06fd297e854d7ad175367e35ea184fd2fcf6c79b8c25/pyqt5_sip-12.17.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b023da906a70af2cf5e6fc1932f441ede07530f3e164dd52c6c2bb5ab7c6f424", size = 281923, upload-time = "2025-10-08T09:15:37.004Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/31/491c45423174a359a4b8a8d84a7b541c453f48497ae928cbe4006bcd3e01/pyqt5_sip-12.17.1-cp313-cp313-win32.whl", hash = "sha256:36dbef482bd638786b909f3bda65b7b3d5cbd6cbf16797496de38bae542da307", size = 49400, upload-time = "2025-10-08T09:11:25.769Z" },
+ { url = "https://files.pythonhosted.org/packages/64/61/e28681dd5200094f7b2e6671e85c02a4d6693da36d23ad7d39ffbc70b15c/pyqt5_sip-12.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:d04e5551bbc3bcec98acc63b3b0618ddcbf31ff107349225b516fe7e7c0a7c8b", size = 57979, upload-time = "2025-10-08T09:08:37.036Z" },
+ { url = "https://files.pythonhosted.org/packages/71/f9/06c09dc94474ffe3f518f80e47fc69d34abf8e4a971ae7e7c667d6ff30a7/pyqt5_sip-12.17.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c49918287e1ad77956d1589f1d3d432a0be7630c646ea02cf652413a48e14458", size = 124400, upload-time = "2025-10-08T08:38:23.927Z" },
+ { url = "https://files.pythonhosted.org/packages/40/ae/be6e338ea427deac5cd81a93f51ae3fb6505d99d6d5e5d5341bcc099327e/pyqt5_sip-12.17.1-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:944a4bf1e1ee18ad03a54964c1c6433fb6de582313a1f0b17673e7203e22fc83", size = 282291, upload-time = "2025-10-08T08:38:25.735Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/a3/8b758518bd0dd5d1581f7a6d522c9b4d9b58d05087b1d0b4dfaad5376434/pyqt5_sip-12.17.1-cp314-cp314-win32.whl", hash = "sha256:99a2935fd662a67748625b1e6ffa0a2d1f2da068b9df6db04fa59a4a5d4ee613", size = 50578, upload-time = "2025-10-08T08:38:28.72Z" },
+ { url = "https://files.pythonhosted.org/packages/40/8c/e96f9877548810b1e537f46fc21ba74552dd4e8c498658114a8353bdf659/pyqt5_sip-12.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:aaa33232cc80793d14fdb3b149b27eec0855612ed66aad480add5ac49b9cee63", size = 59763, upload-time = "2025-10-08T08:38:27.443Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/fd/097af91c0446c4877b2614f3e7e40729c7d0a276c401cef3ff95850cd236/pyqt5_sip-12.17.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6fdc457bd528e5909a5893db0a7dee0066d5f22e08234c9152db0ae6df9a367f", size = 122513, upload-time = "2025-10-08T09:04:18.867Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/cf/58143e15d0bfa1d1450071ad9a2d71adca82ebefbd7729d0c543463d4d31/pyqt5_sip-12.17.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:06ea59741c1bffb198d99b00d26594791f45fb11b10f774c8105aea5962e3835", size = 268959, upload-time = "2025-10-08T09:15:38.323Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9b/8e8f1af1b500f0d4fcd48c9f7ee474731db8b046d3629151e382634eaf59/pyqt5_sip-12.17.1-cp39-cp39-win32.whl", hash = "sha256:b9ef23869d35c6740a95fcb1f387f4aea8d8fac80e19096fbaf1a64e18409c4b", size = 49030, upload-time = "2025-10-08T09:11:26.984Z" },
+ { url = "https://files.pythonhosted.org/packages/64/1e/08bd86000c8294772c6c4aaaf53babd69b9b37e80d1c87583c1fe8eeab5b/pyqt5_sip-12.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:90eed15f19557dfab22e68c7763e3690053cc8dd30d93ade2523d1b5a04a87be", size = 59011, upload-time = "2025-10-08T09:08:37.924Z" },
+]
+
+[[package]]
+name = "pyqt5-sip"
+version = "12.17.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1e/4a/195cf4d2a7e1ff480b4cabcd51aa5c0068c03a19a97282317536e4a82e1e/pyqt5_sip-12.17.2.tar.gz", hash = "sha256:7f66565c2a13d34d8ad6aad08e953d355ea3fe466d991d51aa5a0966a5289f05", size = 104246, upload-time = "2025-12-06T13:19:06.821Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/44/16/96faed1e31658d27979f36f9a56642c6a348ff44a9a35ccbb267c9b66ab3/pyqt5_sip-12.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:43c2bc7e7d19eb67998374c49adbaa8072d4261a286bdf64d08382bacae84fb7", size = 122657, upload-time = "2025-12-06T13:18:37.715Z" },
+ { url = "https://files.pythonhosted.org/packages/11/81/4237700a1154e908c9c5d3be332bf8c58e6a31ed773bccd42ce4248ee297/pyqt5_sip-12.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dda1e7e6840935f17fbeb445ec5da63b9b8e7f673317019397611230faeb81a6", size = 271491, upload-time = "2025-12-06T13:18:38.882Z" },
+ { url = "https://files.pythonhosted.org/packages/be/e3/2bce195fb0229d7d2d6b009c44638ec02f06b7a14e912d053f3d80aa658a/pyqt5_sip-12.17.2-cp310-cp310-win32.whl", hash = "sha256:2dca03cd1d6c2c843e5de4d0a7b33a7812ed37d576ea65249f1a97c17d9f988b", size = 49314, upload-time = "2025-12-06T13:18:41.274Z" },
+ { url = "https://files.pythonhosted.org/packages/80/20/d3baa26aebe4c33f314f7ae4565b4cf922d1d68f98f4919a0e0ad50653e7/pyqt5_sip-12.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:0005227f10a6221d68f764e24181fe15b770da364fd3a67529ac13f589523991", size = 58803, upload-time = "2025-12-06T13:18:40.358Z" },
+ { url = "https://files.pythonhosted.org/packages/47/57/acd812ddfdde9991f4cfe2a738e3646ab66ad2561c3dc0ba8e7541883aff/pyqt5_sip-12.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a6ce4b763f17ac89ef44716dbfa77ed93677ac502aa402989989508715185e74", size = 122716, upload-time = "2025-12-06T13:18:42.822Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/2e/2ff71739ee601347f7b6f6bd3265a259f39d145dfa474c44372d369b06ec/pyqt5_sip-12.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cf8e8a88a3c031dd35bb19c4d7d9a3d65cca84719bed1bc5dd7e2aaf0cf517d2", size = 277063, upload-time = "2025-12-06T13:18:44.62Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/bf/8bef9051e49178e18a0c345de95a982c7a4f3779208ab793381d613ea435/pyqt5_sip-12.17.2-cp311-cp311-win32.whl", hash = "sha256:291d0e2aeddd18081533804150cc59e183b3ab6b4da2b2cf701fdf3ea41ffdda", size = 49323, upload-time = "2025-12-06T13:18:47.51Z" },
+ { url = "https://files.pythonhosted.org/packages/54/d1/377bdc729877f12bdf3841716a4e620aa51b50a0cddcfa8aeecc3a152c9b/pyqt5_sip-12.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:d53bea28b881cd9a4536c27c0658ae182bfb514dc1ff9235d16d10288010fc59", size = 58798, upload-time = "2025-12-06T13:18:45.684Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/3e/f5c7bc43668147ddc00a1a579f22639dffdbfb9470ce3a5bc1cf27e0d541/pyqt5_sip-12.17.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0bd1a8e59124a90a05f078bceeb9d4d93c3986c349030487c202fffde6612969", size = 124612, upload-time = "2025-12-06T13:18:49.614Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/41/63f81a53704425092558f1ec17adbed11787f4322e60a849e0539516b3aa/pyqt5_sip-12.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:55ff374feb4bad783241649c7b946e05d7e83d60b0755526ed8fb25bf54e3408", size = 282364, upload-time = "2025-12-06T13:18:51.179Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/cd/2b749a174e61394085d61cafb7dc3c11ddf40307edfb2d71cb9b71b7f320/pyqt5_sip-12.17.2-cp312-cp312-win32.whl", hash = "sha256:45dc6e2121d175fdab1431c448fd3e88c97caf873a33cb65efa2e9ad0056337b", size = 49521, upload-time = "2025-12-06T13:18:53.155Z" },
+ { url = "https://files.pythonhosted.org/packages/73/ac/7f6d6a6a4505b251f1174092f09d5611c2ed66602c40d3411d93a1d2a95f/pyqt5_sip-12.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:e3bb16e43afd68dd013228075876cf8f8b1a7d86ba67767dd2c6a97be677c18d", size = 58003, upload-time = "2025-12-06T13:18:52.119Z" },
+ { url = "https://files.pythonhosted.org/packages/38/b1/78432c271b2a5477f5fe1ad9eb69cdc482430230b8d552cf5cee393d7862/pyqt5_sip-12.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cfeee3c27f28c091d6a46f8befe9afcfafc76846846bedf1112d403a7299e864", size = 124589, upload-time = "2025-12-06T13:18:54.942Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/d9/6451973300f7dffe70476cad7fc4a59ffe08417ee4add6afb3288c91bd85/pyqt5_sip-12.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b5df33e198d5d7cccc8e081f80eb97b8d70100f887362074a029a6c19cb92c8b", size = 282040, upload-time = "2025-12-06T13:18:57.019Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/1e/241d9ddef5cb1bb3e3b5839b6f8c05ae727e196be82b4646ea4ef9475ef7/pyqt5_sip-12.17.2-cp313-cp313-win32.whl", hash = "sha256:2c0a278b8fc289d34d4e62bbb9ef6da96b45cc9ab3f6886397b1490d2b4a5604", size = 49497, upload-time = "2025-12-06T13:19:00.012Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/33/a393163b6299a7e0743fad86fbcb06cf219878fbdd629ee6cb46d2a4d9f7/pyqt5_sip-12.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:7e0d663b583a4d3ac63c9fbade2228de6ee628b44a025f5fd964b97dbbcbebc9", size = 58075, upload-time = "2025-12-06T13:18:58.069Z" },
+ { url = "https://files.pythonhosted.org/packages/58/29/b4943def737d3f8876bfd4f9af1909892ae1998099695b3e81870c39aaa7/pyqt5_sip-12.17.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6f03c25a18294f2d66befc4f2adf3f35fceba877b937dc8a94783fa7da8b7345", size = 124591, upload-time = "2025-12-06T13:19:02.105Z" },
+ { url = "https://files.pythonhosted.org/packages/18/62/e7ac79bb080d4e5a7d7fea50ca7d9231a7ded07e01f24d4e123f089e1630/pyqt5_sip-12.17.2-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a6d716801d512643b7b1f50dfbdcd16408fe9a6df907d8627b4ad82190604bec", size = 282412, upload-time = "2025-12-06T13:19:03.787Z" },
+ { url = "https://files.pythonhosted.org/packages/92/11/b63ee88ffc2e04af90ced17dbe0d774f5f4e51122c13f8118e565707954e/pyqt5_sip-12.17.2-cp314-cp314-win32.whl", hash = "sha256:c617c29524fdcf826e619d77ffd0d6142622f8422adc2608ecc89edd3e605339", size = 50713, upload-time = "2025-12-06T13:19:05.851Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/70/efe47083dea494613fc41da55f25c07b4e73bb90c98dee8fe87afbfbc303/pyqt5_sip-12.17.2-cp314-cp314-win_amd64.whl", hash = "sha256:b008755d2222a064ec90c525fce5df3fe9d410371e47c43a21c049e07683b7fb", size = 59620, upload-time = "2025-12-06T13:19:04.829Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "7.4.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116, upload-time = "2023-12-31T12:00:18.035Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287, upload-time = "2023-12-31T12:00:13.963Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "pywin32"
+version = "311"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" },
+ { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" },
+ { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" },
+ { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
+ { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
+ { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
+ { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837, upload-time = "2025-07-14T20:12:59.59Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187, upload-time = "2025-07-14T20:13:01.419Z" },
+ { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
+ { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
+ { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" },
+ { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" },
+ { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" },
+]
+
+[[package]]
+name = "pyzmq"
+version = "27.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "implementation_name == 'pypy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" },
+ { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" },
+ { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" },
+ { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" },
+ { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" },
+ { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" },
+ { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" },
+ { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" },
+ { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" },
+ { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" },
+ { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" },
+ { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" },
+ { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" },
+ { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" },
+ { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" },
+ { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" },
+ { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" },
+ { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" },
+ { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" },
+ { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" },
+ { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/4e/782eb6df91b6a9d9afa96c2dcfc5cac62562a68eb62a02210101f886014d/pyzmq-27.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:96c71c32fff75957db6ae33cd961439f386505c6e6b377370af9b24a1ef9eafb", size = 1330426, upload-time = "2025-09-08T23:09:21.03Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/ca/2b8693d06b1db4e0c084871e4c9d7842b561d0a6ff9d780640f5e3e9eb55/pyzmq-27.1.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:49d3980544447f6bd2968b6ac913ab963a49dcaa2d4a2990041f16057b04c429", size = 906559, upload-time = "2025-09-08T23:09:22.983Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/b3/b99b39e2cfdcebd512959780e4d299447fd7f46010b1d88d63324e2481ec/pyzmq-27.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849ca054d81aa1c175c49484afaaa5db0622092b5eccb2055f9f3bb8f703782d", size = 863816, upload-time = "2025-09-08T23:09:24.556Z" },
+ { url = "https://files.pythonhosted.org/packages/61/b2/018fa8e8eefb34a625b1a45e2effcbc9885645b22cdd0a68283f758351e7/pyzmq-27.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3970778e74cb7f85934d2b926b9900e92bfe597e62267d7499acc39c9c28e345", size = 666735, upload-time = "2025-09-08T23:09:26.297Z" },
+ { url = "https://files.pythonhosted.org/packages/01/05/8ae778f7cd7c94030731ae2305e6a38f3a333b6825f56c0c03f2134ccf1b/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da96ecdcf7d3919c3be2de91a8c513c186f6762aa6cf7c01087ed74fad7f0968", size = 1655425, upload-time = "2025-09-08T23:09:28.172Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/ad/d69478a97a3f3142f9dbbbd9daa4fcf42541913a85567c36d4cfc19b2218/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9541c444cfe1b1c0156c5c86ece2bb926c7079a18e7b47b0b1b3b1b875e5d098", size = 2033729, upload-time = "2025-09-08T23:09:30.097Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/6d/e3c6ad05bc1cddd25094e66cc15ae8924e15c67e231e93ed2955c401007e/pyzmq-27.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e30a74a39b93e2e1591b58eb1acef4902be27c957a8720b0e368f579b82dc22f", size = 1891803, upload-time = "2025-09-08T23:09:31.875Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/a7/97e8be0daaca157511563160b67a13d4fe76b195e3fa6873cb554ad46be3/pyzmq-27.1.0-cp39-cp39-win32.whl", hash = "sha256:b1267823d72d1e40701dcba7edc45fd17f71be1285557b7fe668887150a14b78", size = 567627, upload-time = "2025-09-08T23:09:33.98Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/91/70bbf3a7c5b04c904261ef5ba224d8a76315f6c23454251bf5f55573a8a1/pyzmq-27.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c996ded912812a2fcd7ab6574f4ad3edc27cb6510349431e4930d4196ade7db", size = 632315, upload-time = "2025-09-08T23:09:36.097Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/b5/a4173a83c7fd37f6bdb5a800ea338bc25603284e9ef8681377cec006ede4/pyzmq-27.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:346e9ba4198177a07e7706050f35d733e08c1c1f8ceacd5eb6389d653579ffbc", size = 559833, upload-time = "2025-09-08T23:09:38.183Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" },
+ { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" },
+ { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" },
+ { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" },
+ { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" },
+ { url = "https://files.pythonhosted.org/packages/57/f4/c2e978cf6b833708bad7d6396c3a20c19750585a1775af3ff13c435e1912/pyzmq-27.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:722ea791aa233ac0a819fc2c475e1292c76930b31f1d828cb61073e2fe5e208f", size = 836257, upload-time = "2025-09-08T23:10:07.635Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/5f/4e10c7f57a4c92ab0fbb2396297aa8d618e6f5b9b8f8e9756d56f3e6fc52/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:01f9437501886d3a1dd4b02ef59fb8cc384fa718ce066d52f175ee49dd5b7ed8", size = 800203, upload-time = "2025-09-08T23:10:09.436Z" },
+ { url = "https://files.pythonhosted.org/packages/19/72/a74a007cd636f903448c6ab66628104b1fc5f2ba018733d5eabb94a0a6fb/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a19387a3dddcc762bfd2f570d14e2395b2c9701329b266f83dd87a2b3cbd381", size = 758756, upload-time = "2025-09-08T23:10:11.733Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/d4/30c25b91f2b4786026372f5ef454134d7f576fcf4ac58539ad7dd5de4762/pyzmq-27.1.0-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c618fbcd069e3a29dcd221739cacde52edcc681f041907867e0f5cc7e85f172", size = 567742, upload-time = "2025-09-08T23:10:14.732Z" },
+ { url = "https://files.pythonhosted.org/packages/92/aa/ee86edad943438cd0316964020c4b6d09854414f9f945f8e289ea6fcc019/pyzmq-27.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff8d114d14ac671d88c89b9224c63d6c4e5a613fe8acd5594ce53d752a3aafe9", size = 544857, upload-time = "2025-09-08T23:10:16.431Z" },
+]
+
+[[package]]
+name = "qtawesome"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "qtpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/56/57/2f6c402b4cd91a58fd378a5f36be6b5855cf43dc25f77f581e2612e6d558/qtawesome-1.4.0.tar.gz", hash = "sha256:783e414d1317f3e978bf67ea8e8a1b1498bad9dbd305dec814027e3b50521be6", size = 2614365, upload-time = "2025-02-27T22:01:01.864Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a5/ee/6e6c6715129c929af2d95ddb2e9decf54c1beffe58f336911197aacc0448/qtawesome-1.4.0-py3-none-any.whl", hash = "sha256:a4d689fa071c595aa6184171ce1f0f847677cb8d2db45382c43129f1d72a3d93", size = 2595296, upload-time = "2025-02-27T22:00:59.921Z" },
+]
+
+[[package]]
+name = "qtpy"
+version = "2.4.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.36.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version < '3.10'" },
+ { name = "rpds-py", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+]
+
+[[package]]
+name = "roman-numerals"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" },
+]
+
+[[package]]
+name = "roman-numerals-py"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "roman-numerals", marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.27.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" },
+ { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" },
+ { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" },
+ { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" },
+ { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" },
+ { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" },
+ { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" },
+ { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" },
+ { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" },
+ { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" },
+ { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" },
+ { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" },
+ { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" },
+ { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" },
+ { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" },
+ { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" },
+ { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" },
+ { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" },
+ { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" },
+ { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" },
+ { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" },
+ { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" },
+ { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" },
+ { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" },
+ { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" },
+ { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" },
+ { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" },
+ { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" },
+ { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" },
+ { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" },
+ { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" },
+ { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" },
+ { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/6c/252e83e1ce7583c81f26d1d884b2074d40a13977e1b6c9c50bbf9a7f1f5a/rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", size = 372140, upload-time = "2025-08-27T12:15:05.441Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/71/949c195d927c5aeb0d0629d329a20de43a64c423a6aa53836290609ef7ec/rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", size = 354086, upload-time = "2025-08-27T12:15:07.404Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/02/e43e332ad8ce4f6c4342d151a471a7f2900ed1d76901da62eb3762663a71/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", size = 382117, upload-time = "2025-08-27T12:15:09.275Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/05/b0fdeb5b577197ad72812bbdfb72f9a08fa1e64539cc3940b1b781cd3596/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", size = 394520, upload-time = "2025-08-27T12:15:10.727Z" },
+ { url = "https://files.pythonhosted.org/packages/67/1f/4cfef98b2349a7585181e99294fa2a13f0af06902048a5d70f431a66d0b9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", size = 522657, upload-time = "2025-08-27T12:15:12.613Z" },
+ { url = "https://files.pythonhosted.org/packages/44/55/ccf37ddc4c6dce7437b335088b5ca18da864b334890e2fe9aa6ddc3f79a9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", size = 402967, upload-time = "2025-08-27T12:15:14.113Z" },
+ { url = "https://files.pythonhosted.org/packages/74/e5/5903f92e41e293b07707d5bf00ef39a0eb2af7190aff4beaf581a6591510/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", size = 384372, upload-time = "2025-08-27T12:15:15.842Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/e3/fbb409e18aeefc01e49f5922ac63d2d914328430e295c12183ce56ebf76b/rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", size = 401264, upload-time = "2025-08-27T12:15:17.388Z" },
+ { url = "https://files.pythonhosted.org/packages/55/79/529ad07794e05cb0f38e2f965fc5bb20853d523976719400acecc447ec9d/rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", size = 418691, upload-time = "2025-08-27T12:15:19.144Z" },
+ { url = "https://files.pythonhosted.org/packages/33/39/6554a7fd6d9906fda2521c6d52f5d723dca123529fb719a5b5e074c15e01/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", size = 558989, upload-time = "2025-08-27T12:15:21.087Z" },
+ { url = "https://files.pythonhosted.org/packages/19/b2/76fa15173b6f9f445e5ef15120871b945fb8dd9044b6b8c7abe87e938416/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", size = 589835, upload-time = "2025-08-27T12:15:22.696Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/9e/5560a4b39bab780405bed8a88ee85b30178061d189558a86003548dea045/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", size = 555227, upload-time = "2025-08-27T12:15:24.278Z" },
+ { url = "https://files.pythonhosted.org/packages/52/d7/cd9c36215111aa65724c132bf709c6f35175973e90b32115dedc4ced09cb/rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", size = 217899, upload-time = "2025-08-27T12:15:25.926Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/e0/d75ab7b4dd8ba777f6b365adbdfc7614bbfe7c5f05703031dfa4b61c3d6c/rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", size = 228725, upload-time = "2025-08-27T12:15:27.398Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" },
+ { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" },
+ { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" },
+ { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" },
+ { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" },
+ { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" },
+ { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" },
+ { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/ea/5463cd5048a7a2fcdae308b6e96432802132c141bfb9420260142632a0f1/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", size = 371778, upload-time = "2025-08-27T12:16:13.851Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/c8/f38c099db07f5114029c1467649d308543906933eebbc226d4527a5f4693/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", size = 354394, upload-time = "2025-08-27T12:16:15.609Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/79/b76f97704d9dd8ddbd76fed4c4048153a847c5d6003afe20a6b5c3339065/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", size = 382348, upload-time = "2025-08-27T12:16:17.251Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/3f/ef23d3c1be1b837b648a3016d5bbe7cfe711422ad110b4081c0a90ef5a53/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", size = 394159, upload-time = "2025-08-27T12:16:19.251Z" },
+ { url = "https://files.pythonhosted.org/packages/74/8a/9e62693af1a34fd28b1a190d463d12407bd7cf561748cb4745845d9548d3/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", size = 522775, upload-time = "2025-08-27T12:16:20.929Z" },
+ { url = "https://files.pythonhosted.org/packages/36/0d/8d5bb122bf7a60976b54c5c99a739a3819f49f02d69df3ea2ca2aff47d5c/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", size = 402633, upload-time = "2025-08-27T12:16:22.548Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/0e/237948c1f425e23e0cf5a566d702652a6e55c6f8fbd332a1792eb7043daf/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", size = 384867, upload-time = "2025-08-27T12:16:24.29Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/0a/da0813efcd998d260cbe876d97f55b0f469ada8ba9cbc47490a132554540/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", size = 401791, upload-time = "2025-08-27T12:16:25.954Z" },
+ { url = "https://files.pythonhosted.org/packages/51/78/c6c9e8a8aaca416a6f0d1b6b4a6ee35b88fe2c5401d02235d0a056eceed2/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", size = 419525, upload-time = "2025-08-27T12:16:27.659Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/69/5af37e1d71487cf6d56dd1420dc7e0c2732c1b6ff612aa7a88374061c0a8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", size = 559255, upload-time = "2025-08-27T12:16:29.343Z" },
+ { url = "https://files.pythonhosted.org/packages/40/7f/8b7b136069ef7ac3960eda25d832639bdb163018a34c960ed042dd1707c8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", size = 590384, upload-time = "2025-08-27T12:16:31.005Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/06/c316d3f6ff03f43ccb0eba7de61376f8ec4ea850067dddfafe98274ae13c/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", size = 555959, upload-time = "2025-08-27T12:16:32.73Z" },
+ { url = "https://files.pythonhosted.org/packages/60/94/384cf54c430b9dac742bbd2ec26c23feb78ded0d43d6d78563a281aec017/rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", size = 228784, upload-time = "2025-08-27T12:16:34.428Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.30.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" },
+ { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" },
+ { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" },
+ { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" },
+ { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" },
+ { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" },
+ { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" },
+ { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" },
+ { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" },
+ { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" },
+ { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" },
+ { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" },
+ { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" },
+ { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" },
+ { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" },
+ { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" },
+ { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" },
+ { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" },
+ { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" },
+ { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" },
+ { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" },
+ { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" },
+ { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" },
+ { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" },
+ { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" },
+ { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" },
+ { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" },
+ { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" },
+ { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" },
+ { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" },
+ { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" },
+ { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" },
+ { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" },
+ { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" },
+ { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" },
+ { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" },
+ { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" },
+ { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" },
+ { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" },
+ { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" },
+ { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" },
+ { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" },
+ { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" },
+ { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" },
+ { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" },
+ { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" },
+ { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.14.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d4/77/9a7fe084d268f8855d493e5031ea03fa0af8cc05887f638bf1c4e3363eb8/ruff-0.14.11.tar.gz", hash = "sha256:f6dc463bfa5c07a59b1ff2c3b9767373e541346ea105503b4c0369c520a66958", size = 5993417, upload-time = "2026-01-08T19:11:58.322Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f0/a6/a4c40a5aaa7e331f245d2dc1ac8ece306681f52b636b40ef87c88b9f7afd/ruff-0.14.11-py3-none-linux_armv6l.whl", hash = "sha256:f6ff2d95cbd335841a7217bdfd9c1d2e44eac2c584197ab1385579d55ff8830e", size = 12951208, upload-time = "2026-01-08T19:12:09.218Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/5c/360a35cb7204b328b685d3129c08aca24765ff92b5a7efedbdd6c150d555/ruff-0.14.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f6eb5c1c8033680f4172ea9c8d3706c156223010b8b97b05e82c59bdc774ee6", size = 13330075, upload-time = "2026-01-08T19:12:02.549Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/9e/0cc2f1be7a7d33cae541824cf3f95b4ff40d03557b575912b5b70273c9ec/ruff-0.14.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2fc34cc896f90080fca01259f96c566f74069a04b25b6205d55379d12a6855e", size = 12257809, upload-time = "2026-01-08T19:12:00.366Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/e5/5faab97c15bb75228d9f74637e775d26ac703cc2b4898564c01ab3637c02/ruff-0.14.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53386375001773ae812b43205d6064dae49ff0968774e6befe16a994fc233caa", size = 12678447, upload-time = "2026-01-08T19:12:13.899Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/33/e9767f60a2bef779fb5855cab0af76c488e0ce90f7bb7b8a45c8a2ba4178/ruff-0.14.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a697737dce1ca97a0a55b5ff0434ee7205943d4874d638fe3ae66166ff46edbe", size = 12758560, upload-time = "2026-01-08T19:11:42.55Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/84/4c6cf627a21462bb5102f7be2a320b084228ff26e105510cd2255ea868e5/ruff-0.14.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6845ca1da8ab81ab1dce755a32ad13f1db72e7fba27c486d5d90d65e04d17b8f", size = 13599296, upload-time = "2026-01-08T19:11:30.371Z" },
+ { url = "https://files.pythonhosted.org/packages/88/e1/92b5ed7ea66d849f6157e695dc23d5d6d982bd6aa8d077895652c38a7cae/ruff-0.14.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e36ce2fd31b54065ec6f76cb08d60159e1b32bdf08507862e32f47e6dde8bcbf", size = 15048981, upload-time = "2026-01-08T19:12:04.742Z" },
+ { url = "https://files.pythonhosted.org/packages/61/df/c1bd30992615ac17c2fb64b8a7376ca22c04a70555b5d05b8f717163cf9f/ruff-0.14.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590bcc0e2097ecf74e62a5c10a6b71f008ad82eb97b0a0079e85defe19fe74d9", size = 14633183, upload-time = "2026-01-08T19:11:40.069Z" },
+ { url = "https://files.pythonhosted.org/packages/04/e9/fe552902f25013dd28a5428a42347d9ad20c4b534834a325a28305747d64/ruff-0.14.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53fe71125fc158210d57fe4da26e622c9c294022988d08d9347ec1cf782adafe", size = 14050453, upload-time = "2026-01-08T19:11:37.555Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/93/f36d89fa021543187f98991609ce6e47e24f35f008dfe1af01379d248a41/ruff-0.14.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35c9da08562f1598ded8470fcfef2afb5cf881996e6c0a502ceb61f4bc9c8a3", size = 13757889, upload-time = "2026-01-08T19:12:07.094Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/9f/c7fb6ecf554f28709a6a1f2a7f74750d400979e8cd47ed29feeaa1bd4db8/ruff-0.14.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0f3727189a52179393ecf92ec7057c2210203e6af2676f08d92140d3e1ee72c1", size = 13955832, upload-time = "2026-01-08T19:11:55.064Z" },
+ { url = "https://files.pythonhosted.org/packages/db/a0/153315310f250f76900a98278cf878c64dfb6d044e184491dd3289796734/ruff-0.14.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eb09f849bd37147a789b85995ff734a6c4a095bed5fd1608c4f56afc3634cde2", size = 12586522, upload-time = "2026-01-08T19:11:35.356Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/2b/a73a2b6e6d2df1d74bf2b78098be1572191e54bec0e59e29382d13c3adc5/ruff-0.14.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c61782543c1231bf71041461c1f28c64b961d457d0f238ac388e2ab173d7ecb7", size = 12724637, upload-time = "2026-01-08T19:11:47.796Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/41/09100590320394401cd3c48fc718a8ba71c7ddb1ffd07e0ad6576b3a3df2/ruff-0.14.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82ff352ea68fb6766140381748e1f67f83c39860b6446966cff48a315c3e2491", size = 13145837, upload-time = "2026-01-08T19:11:32.87Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/d8/e035db859d1d3edf909381eb8ff3e89a672d6572e9454093538fe6f164b0/ruff-0.14.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:728e56879df4ca5b62a9dde2dd0eb0edda2a55160c0ea28c4025f18c03f86984", size = 13850469, upload-time = "2026-01-08T19:12:11.694Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/02/bb3ff8b6e6d02ce9e3740f4c17dfbbfb55f34c789c139e9cd91985f356c7/ruff-0.14.11-py3-none-win32.whl", hash = "sha256:337c5dd11f16ee52ae217757d9b82a26400be7efac883e9e852646f1557ed841", size = 12851094, upload-time = "2026-01-08T19:11:45.163Z" },
+ { url = "https://files.pythonhosted.org/packages/58/f1/90ddc533918d3a2ad628bc3044cdfc094949e6d4b929220c3f0eb8a1c998/ruff-0.14.11-py3-none-win_amd64.whl", hash = "sha256:f981cea63d08456b2c070e64b79cb62f951aa1305282974d4d5216e6e0178ae6", size = 14001379, upload-time = "2026-01-08T19:11:52.591Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/1c/1dbe51782c0e1e9cfce1d1004752672d2d4629ea46945d19d731ad772b3b/ruff-0.14.11-py3-none-win_arm64.whl", hash = "sha256:649fb6c9edd7f751db276ef42df1f3df41c38d67d199570ae2a7bd6cbc3590f0", size = 12938644, upload-time = "2026-01-08T19:11:50.027Z" },
+]
+
+[[package]]
+name = "scikit-image"
+version = "0.24.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "imageio", marker = "python_full_version < '3.10'" },
+ { name = "lazy-loader", marker = "python_full_version < '3.10'" },
+ { name = "networkx", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "tifffile", version = "2024.8.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5d/c5/bcd66bf5aae5587d3b4b69c74bee30889c46c9778e858942ce93a030e1f3/scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab", size = 22693928, upload-time = "2024-06-18T19:05:31.49Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/82/d4eaa6e441f28a783762093a3c74bcc4a67f1c65bf011414ad4ea85187d8/scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb3bc0264b6ab30b43c4179ee6156bc18b4861e78bb329dd8d16537b7bbf827a", size = 14051470, upload-time = "2024-06-18T19:03:37.385Z" },
+ { url = "https://files.pythonhosted.org/packages/65/15/1879307aaa2c771aa8ef8f00a171a85033bffc6b2553cfd2657426881452/scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9c7a52e20cdd760738da38564ba1fed7942b623c0317489af1a598a8dedf088b", size = 13385822, upload-time = "2024-06-18T19:03:43.996Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/b8/2d52864714b82122f4a36f47933f61f1cd2a6df34987873837f8064d4fdf/scikit_image-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f46e6ce42e5409f4d09ce1b0c7f80dd7e4373bcec635b6348b63e3c886eac8", size = 14216787, upload-time = "2024-06-18T19:03:50.169Z" },
+ { url = "https://files.pythonhosted.org/packages/40/2e/8b39cd2c347490dbe10adf21fd50bbddb1dada5bb0512c3a39371285eb62/scikit_image-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39ee0af13435c57351a3397eb379e72164ff85161923eec0c38849fecf1b4764", size = 14866533, upload-time = "2024-06-18T19:03:56.286Z" },
+ { url = "https://files.pythonhosted.org/packages/99/89/3fcd68d034db5d29c974e964d03deec9d0fbf9410ff0a0b95efff70947f6/scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7", size = 12864601, upload-time = "2024-06-18T19:04:00.868Z" },
+ { url = "https://files.pythonhosted.org/packages/90/e3/564beb0c78bf83018a146dfcdc959c99c10a0d136480b932a350c852adbc/scikit_image-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:272909e02a59cea3ed4aa03739bb88df2625daa809f633f40b5053cf09241831", size = 14020429, upload-time = "2024-06-18T19:04:07.18Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f6/be8b16d8ab6ebf19057877c2aec905cbd438dd92ca64b8efe9e9af008fa3/scikit_image-0.24.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:190ebde80b4470fe8838764b9b15f232a964f1a20391663e31008d76f0c696f7", size = 13371950, upload-time = "2024-06-18T19:04:13.266Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/2e/3a949995f8fc2a65b15a4964373e26c5601cb2ea68f36b115571663e7a38/scikit_image-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c98cc695005faf2b79904e4663796c977af22586ddf1b12d6af2fa22842dc2", size = 14197889, upload-time = "2024-06-18T19:04:17.181Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/96/138484302b8ec9a69cdf65e8d4ab47a640a3b1a8ea3c437e1da3e1a5a6b8/scikit_image-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa27b3a0dbad807b966b8db2d78da734cb812ca4787f7fbb143764800ce2fa9c", size = 14861425, upload-time = "2024-06-18T19:04:27.363Z" },
+ { url = "https://files.pythonhosted.org/packages/50/b2/d5e97115733e2dc657e99868ae0237705b79d0c81f6ced21b8f0799a30d1/scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c", size = 12843506, upload-time = "2024-06-18T19:04:35.782Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/45ad3b8b8ab8d275a48a9d1016c4beb1c2801a7a13e384268861d01145c1/scikit_image-0.24.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6fccceb54c9574590abcddc8caf6cefa57c13b5b8b4260ab3ff88ad8f3c252b3", size = 14101823, upload-time = "2024-06-18T19:04:39.576Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/75/db10ee1bc7936b411d285809b5fe62224bbb1b324a03dd703582132ce5ee/scikit_image-0.24.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ccc01e4760d655aab7601c1ba7aa4ddd8b46f494ac46ec9c268df6f33ccddf4c", size = 13420758, upload-time = "2024-06-18T19:04:45.645Z" },
+ { url = "https://files.pythonhosted.org/packages/87/fd/07a7396962abfe22a285a922a63d18e4d5ec48eb5dbb1c06e96fb8fb6528/scikit_image-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18836a18d3a7b6aca5376a2d805f0045826bc6c9fc85331659c33b4813e0b563", size = 14256813, upload-time = "2024-06-18T19:04:51.68Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/24/4bcd94046b409ac4d63e2f92e46481f95f5006a43e68f6ab2b24f5d70ab4/scikit_image-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8579bda9c3f78cb3b3ed8b9425213c53a25fa7e994b7ac01f2440b395babf660", size = 15013039, upload-time = "2024-06-18T19:04:56.433Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/17/b561823143eb931de0f82fed03ae128ef954a9641309602ea0901c357f95/scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc", size = 12949363, upload-time = "2024-06-18T19:05:02.773Z" },
+ { url = "https://files.pythonhosted.org/packages/93/8e/b6e50d8a6572daf12e27acbf9a1722fdb5e6bfc64f04a5fefa2a71fea0c3/scikit_image-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef04360eda372ee5cd60aebe9be91258639c86ae2ea24093fb9182118008d009", size = 14083010, upload-time = "2024-06-18T19:05:07.582Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/6c/f528c6b80b4e9d38444d89f0d1160797d20c640b7a8cabd8b614ac600b79/scikit_image-0.24.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e9aadb442360a7e76f0c5c9d105f79a83d6df0e01e431bd1d5757e2c5871a1f3", size = 13414235, upload-time = "2024-06-18T19:05:11.58Z" },
+ { url = "https://files.pythonhosted.org/packages/52/03/59c52aa59b952aafcf19163e5d7e924e6156c3d9e9c86ea3372ad31d90f8/scikit_image-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e37de6f4c1abcf794e13c258dc9b7d385d5be868441de11c180363824192ff7", size = 14238540, upload-time = "2024-06-18T19:05:17.481Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/cc/1a58efefb9b17c60d15626b33416728003028d5d51f0521482151a222560/scikit_image-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4688c18bd7ec33c08d7bf0fd19549be246d90d5f2c1d795a89986629af0a1e83", size = 14883801, upload-time = "2024-06-18T19:05:23.231Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/63/233300aa76c65a442a301f9d2416a9b06c91631287bd6dd3d6b620040096/scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69", size = 12891952, upload-time = "2024-06-18T19:05:27.173Z" },
+]
+
+[[package]]
+name = "scikit-image"
+version = "0.25.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "imageio", marker = "python_full_version == '3.10.*'" },
+ { name = "lazy-loader", marker = "python_full_version == '3.10.*'" },
+ { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "packaging", marker = "python_full_version == '3.10.*'" },
+ { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "tifffile", version = "2025.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922, upload-time = "2025-02-18T18:04:11.069Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698, upload-time = "2025-02-18T18:04:15.362Z" },
+ { url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634, upload-time = "2025-02-18T18:04:18.496Z" },
+ { url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545, upload-time = "2025-02-18T18:04:22.556Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908, upload-time = "2025-02-18T18:04:26.364Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" },
+ { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" },
+ { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" },
+ { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" },
+ { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" },
+ { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" },
+ { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" },
+]
+
+[[package]]
+name = "scikit-image"
+version = "0.26.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+]
+dependencies = [
+ { name = "imageio", marker = "python_full_version >= '3.11'" },
+ { name = "lazy-loader", marker = "python_full_version >= '3.11'" },
+ { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "packaging", marker = "python_full_version >= '3.11'" },
+ { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "tifffile", version = "2025.12.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" },
+ { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" },
+ { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" },
+ { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" },
+ { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" },
+ { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" },
+ { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" },
+ { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" },
+ { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" },
+ { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" },
+ { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" },
+ { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" },
+ { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" },
+ { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" },
+ { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" },
+ { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.13.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/59/41b2529908c002ade869623b87eecff3e11e3ce62e996d0bdcb536984187/scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", size = 39328076, upload-time = "2024-05-23T03:19:01.687Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/33/f1307601f492f764062ce7dd471a14750f3360e33cd0f8c614dae208492c/scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", size = 30306232, upload-time = "2024-05-23T03:19:09.089Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/66/9cd4f501dd5ea03e4a4572ecd874936d0da296bd04d1c45ae1a4a75d9c3a/scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", size = 33743202, upload-time = "2024-05-23T03:19:15.138Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/ba/7255e5dc82a65adbe83771c72f384d99c43063648456796436c9a5585ec3/scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f", size = 38577335, upload-time = "2024-05-23T03:19:21.984Z" },
+ { url = "https://files.pythonhosted.org/packages/49/a5/bb9ded8326e9f0cdfdc412eeda1054b914dfea952bda2097d174f8832cc0/scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", size = 38820728, upload-time = "2024-05-23T03:19:28.225Z" },
+ { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588, upload-time = "2024-05-23T03:19:35.661Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805, upload-time = "2024-05-23T03:19:43.081Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687, upload-time = "2024-05-23T03:19:48.799Z" },
+ { url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638, upload-time = "2024-05-23T03:19:55.104Z" },
+ { url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931, upload-time = "2024-05-23T03:20:01.82Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145, upload-time = "2024-05-23T03:20:09.173Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227, upload-time = "2024-05-23T03:20:16.433Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301, upload-time = "2024-05-23T03:20:23.538Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348, upload-time = "2024-05-23T03:20:29.885Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062, upload-time = "2024-05-23T03:20:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311, upload-time = "2024-05-23T03:20:42.086Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493, upload-time = "2024-05-23T03:20:48.292Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955, upload-time = "2024-05-23T03:20:55.091Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/29/c2ea58c9731b9ecb30b6738113a95d147e83922986b34c685b8f6eefde21/scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", size = 39352927, upload-time = "2024-05-23T03:21:01.95Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/c0/e71b94b20ccf9effb38d7147c0064c08c622309fd487b1b677771a97d18c/scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", size = 30324538, upload-time = "2024-05-23T03:21:07.634Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/0f/aaa55b06d474817cea311e7b10aab2ea1fd5d43bc6a2861ccc9caec9f418/scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", size = 33732190, upload-time = "2024-05-23T03:21:14.41Z" },
+ { url = "https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", size = 38612244, upload-time = "2024-05-23T03:21:21.827Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/02/1165905f14962174e6569076bcc3315809ae1291ed14de6448cc151eedfd/scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", size = 38845637, upload-time = "2024-05-23T03:21:28.729Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440, upload-time = "2024-05-23T03:21:35.888Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.15.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" },
+ { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" },
+ { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" },
+ { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" },
+ { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" },
+ { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" },
+ { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" },
+ { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" },
+ { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" },
+ { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" },
+ { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" },
+ { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" },
+ { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" },
+ { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" },
+ { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" },
+ { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" },
+ { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" },
+ { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" },
+ { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" },
+ { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" },
+ { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.16.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+]
+dependencies = [
+ { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881, upload-time = "2025-10-28T17:31:47.104Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012, upload-time = "2025-10-28T17:31:53.411Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935, upload-time = "2025-10-28T17:31:57.361Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466, upload-time = "2025-10-28T17:32:01.875Z" },
+ { url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618, upload-time = "2025-10-28T17:32:06.902Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798, upload-time = "2025-10-28T17:32:12.665Z" },
+ { url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154, upload-time = "2025-10-28T17:32:17.961Z" },
+ { url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540, upload-time = "2025-10-28T17:32:23.907Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107, upload-time = "2025-10-28T17:32:29.921Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272, upload-time = "2025-10-28T17:32:34.577Z" },
+ { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" },
+ { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" },
+ { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" },
+ { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" },
+ { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" },
+ { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" },
+ { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" },
+ { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" },
+ { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" },
+ { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" },
+ { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" },
+ { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" },
+ { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" },
+ { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469, upload-time = "2025-10-28T17:36:08.741Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043, upload-time = "2025-10-28T17:36:16.599Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952, upload-time = "2025-10-28T17:36:22.966Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512, upload-time = "2025-10-28T17:36:29.731Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639, upload-time = "2025-10-28T17:36:37.982Z" },
+ { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729, upload-time = "2025-10-28T17:36:46.547Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251, upload-time = "2025-10-28T17:36:55.161Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681, upload-time = "2025-10-28T17:37:04.1Z" },
+ { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423, upload-time = "2025-10-28T17:38:20.005Z" },
+ { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027, upload-time = "2025-10-28T17:38:24.966Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379, upload-time = "2025-10-28T17:37:14.061Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052, upload-time = "2025-10-28T17:37:21.709Z" },
+ { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183, upload-time = "2025-10-28T17:37:29.559Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174, upload-time = "2025-10-28T17:37:36.306Z" },
+ { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852, upload-time = "2025-10-28T17:37:42.228Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595, upload-time = "2025-10-28T17:37:48.102Z" },
+ { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269, upload-time = "2025-10-28T17:37:53.72Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779, upload-time = "2025-10-28T17:37:59.393Z" },
+ { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" },
+ { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.8.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "7.4.7"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "babel", marker = "python_full_version < '3.10'" },
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version < '3.10'" },
+ { name = "imagesize", marker = "python_full_version < '3.10'" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jinja2", marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "pygments", marker = "python_full_version < '3.10'" },
+ { name = "requests", marker = "python_full_version < '3.10'" },
+ { name = "snowballstemmer", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.10'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.10'" },
+ { name = "tomli", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "8.1.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "babel", marker = "python_full_version == '3.10.*'" },
+ { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version == '3.10.*'" },
+ { name = "imagesize", marker = "python_full_version == '3.10.*'" },
+ { name = "jinja2", marker = "python_full_version == '3.10.*'" },
+ { name = "packaging", marker = "python_full_version == '3.10.*'" },
+ { name = "pygments", marker = "python_full_version == '3.10.*'" },
+ { name = "requests", marker = "python_full_version == '3.10.*'" },
+ { name = "snowballstemmer", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.10.*'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.10.*'" },
+ { name = "tomli", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "8.2.3"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+]
+dependencies = [
+ { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "babel", marker = "python_full_version >= '3.11'" },
+ { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version >= '3.11'" },
+ { name = "imagesize", marker = "python_full_version >= '3.11'" },
+ { name = "jinja2", marker = "python_full_version >= '3.11'" },
+ { name = "packaging", marker = "python_full_version >= '3.11'" },
+ { name = "pygments", marker = "python_full_version >= '3.11'" },
+ { name = "requests", marker = "python_full_version >= '3.11'" },
+ { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" },
+ { name = "snowballstemmer", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" },
+]
+
+[[package]]
+name = "sphinx-rtd-theme"
+version = "3.0.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "sphinxcontrib-jquery" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-bibtex"
+version = "2.6.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "pybtex" },
+ { name = "pybtex-docutils" },
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/de/83/1488c9879f2fa3c2cbd6f666c7a3a42a1fa9e08462bec73281fa6c092cba/sphinxcontrib_bibtex-2.6.5.tar.gz", hash = "sha256:9b3224dd6fece9268ebd8c905dc0a83ff2f6c54148a9235fe70e9d1e9ff149c0", size = 118462, upload-time = "2025-06-27T10:40:14.061Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/a0/3a612da94f828f26cabb247817393e79472c32b12c49222bf85fb6d7b6c8/sphinxcontrib_bibtex-2.6.5-py3-none-any.whl", hash = "sha256:455ea4509642ea0b28ede3721550273626f85af65af01f161bfd8e19dc1edd7d", size = 40410, upload-time = "2025-06-27T10:40:12.274Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-jquery"
+version = "4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" },
+]
+
+[[package]]
+name = "tifffile"
+version = "2024.8.30"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/54/30/7017e5560154c100cad3a801c02adb48879cd8e8cb862b82696d84187184/tifffile-2024.8.30.tar.gz", hash = "sha256:2c9508fe768962e30f87def61819183fb07692c258cb175b3c114828368485a4", size = 365714, upload-time = "2024-08-31T17:32:43.945Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/4f/73714b1c1d339b1545cac28764e39f88c69468b5e10e51f327f9aa9d55b9/tifffile-2024.8.30-py3-none-any.whl", hash = "sha256:8bc59a8f02a2665cd50a910ec64961c5373bee0b8850ec89d3b7b485bf7be7ad", size = 227262, upload-time = "2024-08-31T17:32:41.87Z" },
+]
+
+[[package]]
+name = "tifffile"
+version = "2025.5.10"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version == '3.10.*'",
+]
+dependencies = [
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/44/d0/18fed0fc0916578a4463f775b0fbd9c5fed2392152d039df2fb533bfdd5d/tifffile-2025.5.10.tar.gz", hash = "sha256:018335d34283aa3fd8c263bae5c3c2b661ebc45548fde31504016fcae7bf1103", size = 365290, upload-time = "2025-05-10T19:22:34.386Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/06/bd0a6097da704a7a7c34a94cfd771c3ea3c2f405dd214e790d22c93f6be1/tifffile-2025.5.10-py3-none-any.whl", hash = "sha256:e37147123c0542d67bc37ba5cdd67e12ea6fbe6e86c52bee037a9eb6a064e5ad", size = 226533, upload-time = "2025-05-10T19:22:27.279Z" },
+]
+
+[[package]]
+name = "tifffile"
+version = "2025.12.20"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.11'",
+]
+dependencies = [
+ { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f8/a6/85e8ecfd7cb4167f8bd17136b2d42cba296fbc08a247bba70d5747e2046a/tifffile-2025.12.20.tar.gz", hash = "sha256:cb8a4fee327d15b3e3eeac80bbdd8a53b323c80473330bcfb99418ee4c1c827f", size = 373364, upload-time = "2025-12-21T06:23:54.241Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1b/fe/e59859aa1134fac065d36864752daf13215c98b379cb5d93f954dc0ec830/tifffile-2025.12.20-py3-none-any.whl", hash = "sha256:bc0345a20675149353cfcb3f1c48d0a3654231ee26bd46beebaab4d2168feeb6", size = 232031, upload-time = "2025-12-21T06:23:53.003Z" },
+]
+
+[[package]]
+name = "tinycss2"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "tornado"
+version = "6.5.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" },
+ { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" },
+ { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" },
+ { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" },
+ { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" },
+ { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" },
+]
+
+[[package]]
+name = "traitlets"
+version = "5.14.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
+]
+
+[[package]]
+name = "ty"
+version = "0.0.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b7/85/97b5276baa217e05db2fe3d5c61e4dfd35d1d3d0ec95bfca1986820114e0/ty-0.0.10.tar.gz", hash = "sha256:0a1f9f7577e56cd508a8f93d0be2a502fdf33de6a7d65a328a4c80b784f4ac5f", size = 4892892, upload-time = "2026-01-07T23:00:23.572Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/7a/5a7147ce5231c3ccc55d6f945dabd7412e233e755d28093bfdec988ba595/ty-0.0.10-py3-none-linux_armv6l.whl", hash = "sha256:406a8ea4e648551f885629b75dc3f070427de6ed099af45e52051d4c68224829", size = 9835881, upload-time = "2026-01-07T22:08:17.492Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/7d/89f4d2277c938332d047237b47b11b82a330dbff4fff0de8574cba992128/ty-0.0.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d6e0a733e3d6d3bce56d6766bc61923e8b130241088dc2c05e3c549487190096", size = 9696404, upload-time = "2026-01-07T22:08:37.965Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/cd/9dd49e6d40e54d4b7d563f9e2a432c4ec002c0673a81266e269c4bc194ce/ty-0.0.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e4832f8879cb95fc725f7e7fcab4f22be0cf2550f3a50641d5f4409ee04176d4", size = 9181195, upload-time = "2026-01-07T22:59:07.187Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/b8/3e7c556654ba0569ed5207138d318faf8633d87e194760fc030543817c26/ty-0.0.10-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:6b58cc78e5865bc908f053559a80bb77cab0dc168aaad2e88f2b47955694b138", size = 9665002, upload-time = "2026-01-07T22:08:30.782Z" },
+ { url = "https://files.pythonhosted.org/packages/98/96/410a483321406c932c4e3aa1581d1072b72cdcde3ae83cd0664a65c7b254/ty-0.0.10-py3-none-manylinux_2_24_armv7l.whl", hash = "sha256:83c6a514bb86f05005fa93e3b173ae3fde94d291d994bed6fe1f1d2e5c7331cf", size = 9664948, upload-time = "2026-01-07T23:04:14.655Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/5d/cba2ab3e2f660763a72ad12620d0739db012e047eaa0ceaa252bf5e94ebb/ty-0.0.10-py3-none-manylinux_2_24_i686.whl", hash = "sha256:2e43f71e357f8a4f7fc75e4753b37beb2d0f297498055b1673a9306aa3e21897", size = 10125401, upload-time = "2026-01-07T22:08:28.171Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/67/29536e0d97f204a2933122239298e754db4564f4ed7f34e2153012b954be/ty-0.0.10-py3-none-manylinux_2_24_ppc64le.whl", hash = "sha256:18be3c679965c23944c8e574be0635504398c64c55f3f0c46259464e10c0a1c7", size = 10714052, upload-time = "2026-01-07T22:08:20.098Z" },
+ { url = "https://files.pythonhosted.org/packages/63/c8/82ac83b79a71c940c5dcacb644f526f0c8fdf4b5e9664065ab7ee7c0e4ec/ty-0.0.10-py3-none-manylinux_2_24_s390x.whl", hash = "sha256:5477981681440a35acdf9b95c3097410c547abaa32b893f61553dbc3b0096fff", size = 10395924, upload-time = "2026-01-07T22:08:22.839Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/4c/2f9ac5edbd0e67bf82f5cd04275c4e87cbbf69a78f43e5dcf90c1573d44e/ty-0.0.10-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:e206a23bd887574302138b33383ae1edfcc39d33a06a12a5a00803b3f0287a45", size = 10220096, upload-time = "2026-01-07T22:08:13.171Z" },
+ { url = "https://files.pythonhosted.org/packages/04/13/3be2b7bfd53b9952b39b6f2c2ef55edeb1a2fea3bf0285962736ee26731c/ty-0.0.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4e09ddb0d3396bd59f645b85eab20f9a72989aa8b736b34338dcb5ffecfe77b6", size = 9649120, upload-time = "2026-01-07T22:08:34.003Z" },
+ { url = "https://files.pythonhosted.org/packages/93/e3/edd58547d9fd01e4e584cec9dced4f6f283506b422cdd953e946f6a8e9f0/ty-0.0.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:139d2a741579ad86a044233b5d7e189bb81f427eebce3464202f49c3ec0eba3b", size = 9686033, upload-time = "2026-01-07T22:08:40.967Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/bc/9d2f5fec925977446d577fb9b322d0e7b1b1758709f23a6cfc10231e9b84/ty-0.0.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6bae10420c0abfe4601fbbc6ce637b67d0b87a44fa520283131a26da98f2e74c", size = 9841905, upload-time = "2026-01-07T23:04:21.694Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/b8/5acd3492b6a4ef255ace24fcff0d4b1471a05b7f3758d8910a681543f899/ty-0.0.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7358bbc5d037b9c59c3a48895206058bcd583985316c4125a74dd87fd1767adb", size = 10320058, upload-time = "2026-01-07T22:08:25.645Z" },
+ { url = "https://files.pythonhosted.org/packages/35/67/5b6906fccef654c7e801d6ac8dcbe0d493e1f04c38127f82a5e6d7e0aa0e/ty-0.0.10-py3-none-win32.whl", hash = "sha256:f51b6fd485bc695d0fdf555e69e6a87d1c50f14daef6cb980c9c941e12d6bcba", size = 9271806, upload-time = "2026-01-07T22:08:10.08Z" },
+ { url = "https://files.pythonhosted.org/packages/42/36/82e66b9753a76964d26fd9bc3514ea0abce0a5ba5ad7d5f084070c6981da/ty-0.0.10-py3-none-win_amd64.whl", hash = "sha256:16deb77a72cf93b89b4d29577829613eda535fbe030513dfd9fba70fe38bc9f5", size = 10130520, upload-time = "2026-01-07T23:04:11.759Z" },
+ { url = "https://files.pythonhosted.org/packages/63/52/89da123f370e80b587d2db8551ff31562c882d87b32b0e92b59504b709ae/ty-0.0.10-py3-none-win_arm64.whl", hash = "sha256:7495288bca7afba9a4488c9906466d648ffd3ccb6902bc3578a6dbd91a8f05f0", size = 9626026, upload-time = "2026-01-07T23:04:17.91Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+]
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]