From a219136f6d31b04c767cee285b151c2d6c5b325a Mon Sep 17 00:00:00 2001 From: David Neumann Date: Tue, 15 Apr 2025 12:57:37 +0200 Subject: [PATCH 01/19] Added a Changelog and a Contributors List Added two new documents to the repository: CHANGELOG.md and CONTRIBUTORS.md. The CHANGELOG.md file contains a summary of the changes made in each version of the project, while the CONTRIBUTORS.md file lists all contributors to the project along with their contributions. --- CHANGELOG.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTORS.md | 6 ++++ 2 files changed, 88 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTORS.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..14ab786 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +# Changelog + +## v0.3.0 + +*Release date to be determined.* + +### General (v0.3.0) + +- Added this changelog, as well as a contributors list, which contains a list of all people that made contributions to the project. + +## v0.2.1 + +*Released on June 21, 2022.* + +### General (v0.2.1) + +- Fixed a small typo in the read me by @p16i in #8. +- Clarified the maximum number of neighbors in the `SparseKNN` affinity by replacing the confusing `if`-`else` expression with the `min` function by @chr5tphr in #10. +- Added a new logo, which has a design similar to the logo of ViRelAy and added a link to the documentation, as well as some badges to the read me by @chr5tphr in #12. + +### CI/CD (v0.2.1) + +- Added a GitHub Actions workflow, which runs the unit tests and builds the documentation by @chr5tphr in #13. + - Fixed tests and PyLint errors + - Fixed various PyLint style errors, mainly concerning the usage of f-strings instead of `format`. + - Fixed the use of `sklearn.datasets.load_digits`, which now requires keyword-arguments. + - Added `disable=unspecified-encoding` to the PyLint configuration file `pylintrc`, as this rule has a lot of trouble with false-positives. + - Added a GitHub Actions workflow. + - Defined the requirements for docs and tests (mostly) in the `setup.py` using `extras_require`. + - Added a GitHub Actions workflow for tox. + - Updated the `.readthedocs.yaml` configuration file to use the `extras_require` "docs" in the `setup.py` + - Removed the `docs/requirements.txt`, which is no longer needed. + - Added a "tests" badge to the read me. + - Moved the documentation from imgmath to MathJax. + - Moved the `corelay` package directory to `src/corelay` to fix the coverage report and prevent other pitfalls. + - Change the linkcode URL in the documentation to correctly point to `src/corelay` on GitHub. + - Updated the outdated PyLint configuration by removing deprecated disables. + +### Documentation (v0.2.1) + +- Added Sphinx-based documentation by @chr5tphr in #11. + - Fixed the Docstrings and some NumPyDoc issues. + - Removed trailing "s" after backticks. + - Added missing docstring headers. + - Used double backticks for inline-code. + - Added Sphinx documentation. + - Added an index page, with a very brief explanation, installation instructions, contents, and a reference to our paper. + - Added an API reference with AutoSummary and AutoDoc. + - Added a getting started section, which briefly and practically demonstrates Params, Processors, Tasks and Pipelines. + - Added a bibliography with reference to our paper. + - Added a favicon.svg, which currently is an "S", referring to the previous name of CoRelAy, which was Sprincl. + - Ignored properties in AutoDoc to avoid doubling for now, since they are all documented as class attributes. + - Added more Python versions to the tox configuration and integrated the building of the Sphinx documentation. + - Added support for Python 3.7-3.9. + - Moved flake8 to bottom, and only execute it in the Python 3.9 environment. + - Reformated the PyTest command and added configuration for test coverage. + - Added a new docs environment to build the Sphinx documentation. + - Added a new coverage environment. + - Made the PyLint command locations more explicit and only execute it in the Python 3.9 environment. + - Added a PyTest configuration. + - Added configuration the configuration for test coverage. + - Added the configuration for Read the Docs: `.readthedocs.yaml`, which includes the build setup required for publishing the documentation to `readthedocs.org`. +- Remove outdated `requirements.txt`, which was supposed to be removed in #13 by @chr5tphr in #14. +- Remove "Sprincl" references, which was still used in two files, although the project has been renamed to CoRelAy by @chr5tphr in #15. + +## v0.2.0 + +*Released on June 29, 2021.* + +- Added a reference to the ClArC paper with a link and BibTeX in the read me by @chr5tphr in #1. +- Fixed submodules not being found and extra install by @chr5tphr in #2. + - Installing `extra_require` packages must be installed somewhat differently than was shown in the read me, this was updated. Submodules of CoRelAy were also not found when directly installing from an URL. Using Setuptools `find_packages` with a specified include fixed this. +- Example fixes by @sebastian-lapuschkin in #3. + - The imports from `sprincl` were fixed to imports from `corelay`. + - SciKit-Image was updated and the usage of the `compare` function from `skimage.measure` was updated to the `structural_similarity` from `skimage.metrics`. +- Added a perplexity parameter to TSNE by @sebastian-lapuschkin in #4. + - `TSNE` now has an optional perplexity parameter. + - The default value is set to the same default of that `sklearn.manifold.TSNE` has, which is 30. + - Lower perplexity means more focus on local structures, whereas higher perplexity means higher consideration of global structures. +- Updated the paper in the read me from the old paper to the new paper by @lecode-official in #5. +- Added LGPLv3+ as the license of the project by @chr5tphr in #6. +- Prepared the project for upload to PyPI by @chr5tphr in #7. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..45efdf9 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,6 @@ +# ViRelAy Contributors + +- [Christopher J. Anders](https://github.com/chr5tphr) +- [David Neumann](https://github.com/lecode-official) +- [Sebastian Lapuschkin](https://github.com/sebastian-lapuschkin) +- [Pattarawat Chormai](https://github.com/heytitle) From 5056dd530ccbcfe96700f14f4f9ef6fa205457ed Mon Sep 17 00:00:00 2001 From: David Neumann Date: Fri, 11 Apr 2025 21:54:29 +0200 Subject: [PATCH 02/19] Added Spell Checking Added a CSpell configuration for spell-checking the contents of the repository, checked all files, and corrected any spelling mistakes. The spell-checking was integrated into the GitHub Actions workflow, so that the spelling is checked on every push and pull request to the master branch. The documentation still needs to be updated to reflect the new spell-checking process. Furthermore, removed LaTeX commands for accented characters from the bibliography file, as they we are using Pybtex to handle the bibliography and it has full Unicode support. --- .github/workflows/tests.yml | 18 + .gitignore | 59 +- CHANGELOG.md | 1 + README.md | 25 +- docs/source/bibliography.bib | 20 +- example/corelay_basics.py | 2 +- example/hdf5_structure.py | 8 +- example/memoize_spectral_pipeline.py | 2 +- pylintrc | 2 +- src/corelay/io/hashing.py | 2 +- src/corelay/io/storage.py | 6 +- src/corelay/pipeline/base.py | 6 +- src/corelay/plugboard.py | 30 +- src/corelay/processor/base.py | 8 +- src/corelay/processor/flow.py | 2 +- tests/corelay/pipeline/test_base.py | 22 +- tests/corelay/pipeline/test_spectral.py | 10 +- tests/corelay/processor/test_base.py | 10 +- tests/corelay/processor/test_clustering.py | 4 +- tests/corelay/test_base.py | 4 +- tests/corelay/test_plugboard.py | 60 +- tests/corelay/test_tracker.py | 2 +- tests/cspell/.cspell.json | 212 ++++ tests/cspell/package-lock.json | 1166 ++++++++++++++++++++ tests/cspell/package.json | 17 + 25 files changed, 1575 insertions(+), 123 deletions(-) create mode 100644 tests/cspell/.cspell.json create mode 100644 tests/cspell/package-lock.json create mode 100644 tests/cspell/package.json diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f3dd9d3..22264c1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,6 +63,24 @@ jobs: - name: Run test run: tox --skip-pkg-install -e ${{ matrix.tox_env }} + spell-check: + name: Spell-Check Repository + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install NodeJS + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install CSpell Global Configuration + run: npm --prefix tests/cspell install + - name: Run CSpell Spell Checker + run: npm --prefix tests/cspell run cspell + docs: name: docs runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 17e7d6e..558fb30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,62 @@ -# IDEs and Code editors -.vscode +# Operation system file preview databases +.DS_Store +Thumbs.db + +# IDE and code editor configurations +.vs/ +.vscode/ .idea -.venv +*.sublime-workspace .exrc +.vimrc + +# Ignores Python virtual environments +.*venv/ -# Python output +# Build output +__pycache__ .python *.egg-info -__pycache__ - -# Setup output +__pycache__/ build/ dist/ -# Results output -result +# MyPy type information cache +.mypy_cache -# Testing +# Testing and coverage output .tox .coverage .coverage.* +.pytest_cache +htmlcov + +# Dependencies of the frontend installed via NPM +node_modules +npm-debug.log + +# Configuration file for HTML-Validate, which are symlinked into the root directory, because the Visual Studio Code extension for HTML-Validate +# requires the configuration file to be in the root directory +.htmlvalidate.js + +# Documentation build output and autogenerated files +docs/build +docs/doctree +docs/source/_generated +docs/source/api-reference/* +!docs/source/api-reference/index.rst + +# Trained models and datasets/attributions/analyses +*.pt +*.h5 +*.hdf5 +project.yaml +label-map.json +test-project + +# To do lists +TODO.md + +# Results output +result diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ab786..27c2df1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### General (v0.3.0) - Added this changelog, as well as a contributors list, which contains a list of all people that made contributions to the project. +- Added a CSpell configuration for spell-checking the contents of the repository, checked all files, and corrected all spelling mistakes. ## v0.2.1 diff --git a/README.md b/README.md index dce7dd7..45e7122 100644 --- a/README.md +++ b/README.md @@ -9,24 +9,25 @@ CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (Task) with default operations (Processor). -Any step of the pipeline may then be indiviually changed by assigning a new operator (Processor). +Any step of the pipeline may then be individually changed by assigning a new operator (Processor). Processors have Params which define their operation. CoRelAy was created to quickly implement pipelines to generate analysis data which can then be visualized using ViRelAy. If you find CoRelAy useful for your research, why not cite our related [paper](https://arxiv.org/abs/2106.13200): -``` + +```bibtex @article{anders2021software, - author = {Anders, Christopher J. and - Neumann, David and - Samek, Wojciech and - Müller, Klaus-Robert and - Lapuschkin, Sebastian}, - title = {Software for Dataset-wide XAI: From Local Explanations to Global Insights with {Zennit}, {CoRelAy}, and {ViRelAy}}, - journal = {CoRR}, - volume = {abs/2106.13200}, - year = {2021}, + author = {Anders, Christopher J. and + Neumann, David and + Samek, Wojciech and + Müller, Klaus-Robert and + Lapuschkin, Sebastian}, + title = {Software for Dataset-wide XAI: From Local Explanations to Global Insights with {Zennit}, {CoRelAy}, and {ViRelAy}}, + journal = {CoRR}, + volume = {abs/2106.13200}, + year = {2021}, } ``` @@ -131,7 +132,7 @@ def main(): start_time = time.perf_counter() # Processors flagged with "is_output=True" will be accumulated in the output - # the output will be a tree of tuples, with the same hierachy as the pipeline + # the output will be a tree of tuples, with the same hierarchy as the pipeline # (i.e. clusterings here contains a tuple of the k-means outputs) clusterings, tsne = pipeline(data) diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib index 28496c1..4592128 100644 --- a/docs/source/bibliography.bib +++ b/docs/source/bibliography.bib @@ -1,14 +1,12 @@ @article{anders2021software, - author = {Christopher J. Anders and - David Neumann and - Wojciech Samek and - Klaus{-}Robert M{\"{u}}ller and - Sebastian Lapuschkin}, - title = {Software for Dataset-wide {XAI:} From Local Explanations to Global - Insights with Zennit, CoRelAy, and ViRelAy}, - journal = {CoRR}, - volume = {abs/2106.13200}, - year = {2021}, - url = {https://arxiv.org/abs/2106.13200}, + author = {Anders, Christopher J. and + Neumann, David and + Samek, Wojciech and + Müller, Klaus-Robert and + Lapuschkin, Sebastian}, + title = {Software for Dataset-wide XAI: From Local Explanations to Global Insights with {Zennit}, {CoRelAy}, and {ViRelAy}}, + journal = {CoRR}, + volume = {abs/2106.13200}, + year = {2021}, } diff --git a/example/corelay_basics.py b/example/corelay_basics.py index 7251eec..960b249 100644 --- a/example/corelay_basics.py +++ b/example/corelay_basics.py @@ -28,7 +28,7 @@ class MyPipeline(Pipeline): # supplied with the same name in __init__ as a keyword argument. The first value is an optional expected Process # type, second is a default value, which has to be an instance of that type. If the default argument is not a # Process, it will be converted to a FunctionProcessor by default, functions fed to FunctionProcessors are by - # default not bound to the class. To bind them, we can suppy `bind_method=True` to the FunctionProcessor. Supplying + # default not bound to the class. To bind them, we can supply `bind_method=True` to the FunctionProcessor. Supplying # it to the task changes the default value of the Processor before creation: prepreprocess = Task(proc_type=FunctionProcessor, default=(lambda self, x: x * 2), bind_method=True) # Otherwise, we do not need to supply `self` for the default function: diff --git a/example/hdf5_structure.py b/example/hdf5_structure.py index 91ace70..e414686 100644 --- a/example/hdf5_structure.py +++ b/example/hdf5_structure.py @@ -93,9 +93,9 @@ def make_group_example(): # we chose 2 clusters g_clu['/my_clustering/#clusters'] = 2 - # we define a protoype for our clustering + # we define a prototype for our clustering g_clu['/my_clustering/prototype/average/name'] = 'My Random Prototype' - # for demonstration purposes, we use random data here. the first dimonsion is the number of clusters + # for demonstration purposes, we use random data here. the first dimension is the number of clusters g_clu['/my_clustering/prototype/average/root'] = np.random.normal(size=(2, 32, 32)).astype(np.float32) @@ -167,9 +167,9 @@ def make_dataset_example(): # we chose 2 clusters g_clu['/my_clustering/#clusters'] = 2 - # we define a protoype for our clustering + # we define a prototype for our clustering g_clu['/my_clustering/prototype/average/name'] = 'My Random Prototype' - # for demonstration purposes, we use random data here. the first dimonsion is the number of clusters + # for demonstration purposes, we use random data here. the first dimension is the number of clusters g_clu['/my_clustering/prototype/average/root'] = np.random.normal(size=(2, 32, 32)).astype(np.float32) diff --git a/example/memoize_spectral_pipeline.py b/example/memoize_spectral_pipeline.py index 41f8494..cb1ac20 100644 --- a/example/memoize_spectral_pipeline.py +++ b/example/memoize_spectral_pipeline.py @@ -71,7 +71,7 @@ def main(): start_time = time.perf_counter() # Processors flagged with "is_output=True" will be accumulated in the output - # the output will be a tree of tuples, with the same hierachy as the pipeline + # the output will be a tree of tuples, with the same hierarchy as the pipeline # (i.e. clusterings here contains a tuple of the k-means outputs) clusterings, tsne = pipeline(data) diff --git a/pylintrc b/pylintrc index 952a900..1e2ec4b 100644 --- a/pylintrc +++ b/pylintrc @@ -485,7 +485,7 @@ allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. +# only in one or another interpreter, leading to false positives when analyzed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma. diff --git a/src/corelay/io/hashing.py b/src/corelay/io/hashing.py index 3f0a758..58e6b2d 100644 --- a/src/corelay/io/hashing.py +++ b/src/corelay/io/hashing.py @@ -21,7 +21,7 @@ class Tensor: class Hasher(MetroHash128): """Hasher object with a write function for file-like updates""" def write(self, data): - """Update using write for file-like behaviour""" + """Update using write for file-like behavior""" self.update(data) return len(data) diff --git a/src/corelay/io/storage.py b/src/corelay/io/storage.py index cc001f2..496efba 100644 --- a/src/corelay/io/storage.py +++ b/src/corelay/io/storage.py @@ -49,7 +49,7 @@ def __init__(self, h5group): def read(self, data_in, meta): """Read output from a hashed h5 group, with hash of (data_in, meta)""" def _iterread(base): - """Iteratively read from HDF5 Group into tuple hierachy of ndarrays""" + """Iteratively read from HDF5 Group into tuple hierarchy of ndarrays""" if isinstance(base, h5py.Group): return tuple(_iterread(base[key]) for key in sorted(base)) if isinstance(base, h5py.Dataset): @@ -66,7 +66,7 @@ def _iterread(base): def write(self, data_out, data_in, meta): """Write output to a hashed h5 group, with hash of (data_in, meta)""" def _iterwrite(data, group, elem): - """Iteratively write to a HDF5 Group from a tuple hierachy of ndarrays""" + """Iteratively write to a HDF5 Group from a tuple hierarchy of ndarrays""" if isinstance(data, tuple): g_new = group.require_group(elem) for n, array in enumerate(data_out): @@ -77,7 +77,7 @@ def _iterwrite(data, group, elem): raise TypeError('Unsupported output type!') def _iterhash(base): - """Iteratively hash from tuple hierachy into tuple hierachy of hashes""" + """Iteratively hash from tuple hierarchy into tuple hierarchy of hashes""" if isinstance(base, tuple): return tuple(_iterhash(obj) for obj in base) return ext_hash(base) diff --git a/src/corelay/pipeline/base.py b/src/corelay/pipeline/base.py index e7ddf34..cac4d5e 100644 --- a/src/corelay/pipeline/base.py +++ b/src/corelay/pipeline/base.py @@ -124,17 +124,17 @@ def checkpoint_processes(self): return checkpoint_processes def from_checkpoint(self): - """Re-evaluate from last checkpointed :obj:`Processor` using its respective output. + """Re-evaluate from last check-pointed :obj:`Processor` using its respective output. Returns ------- object - Output of the whole pipeline, starting from checkpointed :obj:`Processor` closest to output. + Output of the whole pipeline, starting from check-pointed :obj:`Processor` closest to output. Raises ------ RuntimeError - If the checkpointed :obj:`Processor` closest to output does not have any + If the check-pointed :obj:`Processor` closest to output does not have any `checkpoint_data` store, i.e. the :obj:`Processor` was never called once after being declared a checkpoint. """ diff --git a/src/corelay/plugboard.py b/src/corelay/plugboard.py index 58fe83b..50b1c04 100644 --- a/src/corelay/plugboard.py +++ b/src/corelay/plugboard.py @@ -3,7 +3,7 @@ class EmptyInit: - """Empty Init is a class intened to be inherited as a last step down the MRO, to catch any remaining positional + """Empty Init is a class intended to be inherited as a last step down the MRO, to catch any remaining positional and/or keyword arguments and thus raise proper Exceptions. """ # Following is not useless, since this super delegation causes python to raise a more informative exception. @@ -32,7 +32,7 @@ class Slot(EmptyInit): default : :obj:`dtype` Default parameter value, should be an instance of (one of) :obj:`dtype`. optional : bool - Non-mutuable. True, if Slot has a non-None default value, e.g. is optional. + Non-mutable. True, if Slot has a non-None default value, e.g. is optional. See Also -------- @@ -51,7 +51,7 @@ def __init__(self, dtype=object, default=None, **kwargs): Default plug value, should be an instance of (one of) :obj:`dtype`. May be `None` to indicate that no default value has been set. **kwargs : - Keyword arguments passed down to potential entries below Slot in the MRO, for cooperativities' sake. In + Keyword arguments passed down to potential entries below Slot in the MRO, for cooperativity's sake. In normal cases, this next class will be `EmptyInit`, and thus accept no more kwargs. """ @@ -215,9 +215,9 @@ def __call__(self, obj=None, default=None): Parameters ---------- obj : object - Value to intialize the newly created Plug container's object value to. + Value to initialize the newly created Plug container's object value to. default : object - Value to intialize the newly created Plug container's default value to. + Value to initialize the newly created Plug container's default value to. Returns ------- @@ -266,7 +266,7 @@ def __init__(self, slot, obj=None, default=None, **kwargs): default : object Plug-dependent lower-priority object held in the container. If not set, self.fallback is returned. **kwargs : - Keyword arguments passed down to potential entries below Plug in the MRO, for cooperativities' sake. In + Keyword arguments passed down to potential entries below Plug in the MRO, for cooperativity's sake. In normal cases, this next class will be `EmptyInit`, and thus accept no more kwargs. """ @@ -310,17 +310,17 @@ def slot(self, value): @property def dtype(self): - """Get associated Slot's dtype. Non-mutuable.""" + """Get associated Slot's dtype. Non-mutable.""" return self.slot.dtype @property def optional(self): - """Get whether container has default values. Non-mutuable.""" + """Get whether container has default values. Non-mutable.""" return self.default is not None @property def fallback(self): - """Get associated Slot's default value. Non-mutuable.""" + """Get associated Slot's default value. Non-mutable.""" return self.slot.default @property @@ -412,7 +412,7 @@ def _get_plug(self, name, default=None): return slot.get_plug(self._instance, default=default) def __get__(self, instance, owner): - """Return a new instance of SlotDefaultAccess, initialized with the priovided instance value.""" + """Return a new instance of SlotDefaultAccess, initialized with the provided instance value.""" return type(self)(instance) def __set__(self, instance, value): @@ -441,8 +441,8 @@ def __dir__(self): class Plugboard(Tracker, EmptyInit): - """Optional Manager class for Slots. Uses SlotDefaultAccess to access Plug default values. Also intializes Plug - container object values during instatiation by keywords. + """Optional Manager class for Slots. Uses SlotDefaultAccess to access Plug default values. Also initializes Plug + container object values during instantiation by keywords. Parameters ---------- @@ -452,7 +452,7 @@ class Plugboard(Tracker, EmptyInit): See Also -------- :obj:`Slot` - :obj:`SlotDefaultAcces` + :obj:`SlotDefaultAccess` :obj:`Plug` """ @@ -464,8 +464,8 @@ def __init__(self, **kwargs): Parameters ---------- **kwargs : - Keyword arguements to initialize Slots. Only keyword arguments which correspond to class' Slot attribute - names are processed. All other keyword arguements are passed to the next class' __init__ method in the MRO. + Keyword arguments to initialize Slots. Only keyword arguments which correspond to class' Slot attribute + names are processed. All other keyword arguments are passed to the next class' __init__ method in the MRO. """ slots = self.collect(Slot) diff --git a/src/corelay/processor/base.py b/src/corelay/processor/base.py index 0d76a18..0229722 100644 --- a/src/corelay/processor/base.py +++ b/src/corelay/processor/base.py @@ -21,7 +21,7 @@ class Processor(Plugboard): Defines whether the Processor should yield an output for a Pipeline. is_checkpoint : bool Assigned as :obj:`Param`, will be assigned as an instance attribute in `__init__`. - Defines whether checkpointed pipeline computations should start at this point, if there exists a previously + Defines whether check-pointed pipeline computations should start at this point, if there exists a previously computed checkpoint value. checkpoint_data : object If this Processor is a checkpoint, and if the Processor was called at least once, stores the output of this @@ -41,7 +41,7 @@ def __init__(self, *args, **kwargs): is_checkpoint : bool Whether the Processor should yield an output for a Pipeline. is_output : bool - Whether checkpointed pipeline computations should start at this point, if there exists a previously computed + Whether check-pointed pipeline computations should start at this point, if there exists a previously computed checkpoint value. *args : list Params that have been flagged as positional, in their order of declaration. @@ -75,7 +75,7 @@ def function(self, data): """ def __call__(self, data): - """Apply `self.funtion` on input data, save output if `self.is_checkpoint` + """Apply `self.function` on input data, save output if `self.is_checkpoint` Parameters ---------- @@ -114,7 +114,7 @@ def param_values(self): return self.collect_attr(Param) def identifiers(self): - """Returns a dict containing the class qualifer name, as well all Parameters marked as identifiers with their + """Returns a dict containing the class qualifier name, as well all Parameters marked as identifiers with their values Returns diff --git a/src/corelay/processor/flow.py b/src/corelay/processor/flow.py index 9896060..9a41436 100644 --- a/src/corelay/processor/flow.py +++ b/src/corelay/processor/flow.py @@ -10,7 +10,7 @@ class Shaper(Processor): Attributes ---------- indices : iterable of (int or iterable of int) - Iterable of indices to copy/ extract. The resuling output will be a tuple with the same member shape. Each index + Iterable of indices to copy/ extract. The resulting output will be a tuple with the same member shape. Each index may be passed an arbitrary amount of times. Outer tuples allow ints and tuples, inner tuples only allow ints. Examples diff --git a/tests/corelay/pipeline/test_base.py b/tests/corelay/pipeline/test_base.py index 540b49c..4c3f72c 100644 --- a/tests/corelay/pipeline/test_base.py +++ b/tests/corelay/pipeline/test_base.py @@ -50,29 +50,29 @@ class TestTask: """Test class for Task""" @staticmethod def test_instantiation_default(): - """Instatiation without any arguments should succeed""" + """instantiation without any arguments should succeed""" Task() @staticmethod def test_instantiation_arguments(processor_type): - """Instatiating with correct arguments should succeed""" + """Instantiating with correct arguments should succeed""" Task(proc_type=processor_type, default=processor_type(), is_output=True) @staticmethod def test_proc_type_no_proc(): - """Instatiating with a proc_type which is not a subclass for Processor should raise a TypeError""" + """Instantiating with a proc_type which is not a subclass for Processor should raise a TypeError""" with pytest.raises(TypeError): Task(proc_type=FunctionType, default=(lambda x: x)) @staticmethod def test_default_no_proc(): - """Instatiating with a default value not of type Processor should fail""" + """Instantiating with a default value not of type Processor should fail""" with pytest.raises(TypeError): Task(proc_type=Processor, default='bla') @staticmethod def test_proc_type_default_type_mismatch(processor_type): - """Instatiating with a default value not of type proc_type should raise a TypeError""" + """Instantiating with a default value not of type proc_type should raise a TypeError""" with pytest.raises(TypeError): Task(proc_type=processor_type, default=(lambda x: x)) @@ -94,18 +94,18 @@ def test_assigned_default(processor_type): class TestPipeline: """Test class for Pipeline""" @staticmethod - def test_instatiation_base(): - """Instatiation of the base class without any arguments should succeed""" + def test_instantiation_base(): + """instantiation of the base class without any arguments should succeed""" Pipeline() @staticmethod - def test_instatiation_default(pipeline_type): - """Instatiation of a custom subclass without any arguments should succeed""" + def test_instantiation_default(pipeline_type): + """instantiation of a custom subclass without any arguments should succeed""" pipeline_type() @staticmethod - def test_instatiation_arguments(pipeline_type, processor_type): - """Instatiation with correct arguments should succeed""" + def test_instantiation_arguments(pipeline_type, processor_type): + """instantiation with correct arguments should succeed""" pipeline_type(task_1=lambda x: x + 2, task_2=processor_type()) @staticmethod diff --git a/tests/corelay/pipeline/test_spectral.py b/tests/corelay/pipeline/test_spectral.py index 53c62b5..e354dc6 100644 --- a/tests/corelay/pipeline/test_spectral.py +++ b/tests/corelay/pipeline/test_spectral.py @@ -66,12 +66,12 @@ def k_clusters(): class TestSpectral: """Test class for SpectralClustering""" @staticmethod - def test_spectral_embedding_instatiation(): + def test_spectral_embedding_instantiation(): """test whether we can instantiate a spectral embedding instance successfully""" SpectralEmbedding() @staticmethod - def test_spectral_clustering_instatiation(): + def test_spectral_clustering_instantiation(): """test whether we can instantiate a spectral clustering instance successfully""" SpectralClustering() @@ -83,7 +83,7 @@ def test_data_generation(spiral_data): @staticmethod def test_spectral_embedding_default_params(spiral_data): - """test wheter the SE operates on data all the way through, using its default parameters.""" + """test whether the SE operates on data all the way through, using its default parameters.""" pipeline = SpectralEmbedding() output = pipeline(spiral_data) assert isinstance(output, tuple), f'Expected tuple type output, got {type(output)}' @@ -103,11 +103,11 @@ def test_spectral_embedding_default_params(spiral_data): @staticmethod def test_spectral_clustering_default_params(spiral_data): - """test wheter the SC operates on data all the way through, using its default parameters.""" + """test whether the SC operates on data all the way through, using its default parameters.""" pipeline = SpectralClustering() output = pipeline(spiral_data) assert isinstance(output, tuple), f'Expected tuple type output, got {type(output)}' - assert len(output) == 2, f'Expected output lenght of 2, got {len(output)}' + assert len(output) == 2, f'Expected output length of 2, got {len(output)}' eigenstuff, labels = output assert isinstance(eigenstuff, tuple), ( diff --git a/tests/corelay/processor/test_base.py b/tests/corelay/processor/test_base.py index 3f4c98c..0157161 100644 --- a/tests/corelay/processor/test_base.py +++ b/tests/corelay/processor/test_base.py @@ -66,12 +66,12 @@ def test_params_tracked(processor_type): @staticmethod def test_creation(processor_type): - """Processors should instatiatiate properly in all cases""" + """Processors should instantiate properly in all cases""" processor_type() @staticmethod def test_instance_assign(processor_type, kwargs): - """Parameter values passed as keyword arguments during instatiation should be properly set""" + """Parameter values passed as keyword arguments during instantiation should be properly set""" processor = processor_type(**kwargs) assert all(getattr(processor, key) == val for key, val in kwargs.items()) @@ -165,7 +165,7 @@ def test_reset_defaults(processor_type): @staticmethod def test_reset_defaults_assigned(processor_type): - """Resetting Param default values should go back to returning instatiation-time default values""" + """Resetting Param default values should go back to returning instantiation-time default values""" proc = processor_type(param_1='soup', param_2=2) proc.update_defaults(param_2=1) proc.reset_defaults() @@ -183,7 +183,7 @@ class TestFunctionProcessor: """Test class for FunctionProcessor""" @staticmethod def test_instantiation(unbound_function): - """Instatiation should succeed with an unbound function as a keyword argument""" + """instantiation should succeed with an unbound function as a keyword argument""" FunctionProcessor(function=unbound_function) @staticmethod @@ -228,7 +228,7 @@ def test_function(unbound_function): @staticmethod def test_invalid(): - """Passing anythin but a callable or Processor should raise a TypeError""" + """Passing anything but a callable or Processor should raise a TypeError""" with pytest.raises(TypeError): ensure_processor('mummy') diff --git a/tests/corelay/processor/test_clustering.py b/tests/corelay/processor/test_clustering.py index c77aabe..482818a 100644 --- a/tests/corelay/processor/test_clustering.py +++ b/tests/corelay/processor/test_clustering.py @@ -76,7 +76,7 @@ def test_embedding_on_distances_agg_clustering(distances): assert (np.unique(emb, return_counts=True)[1] / 1000).std() < 0.05 -def test_denrogram_creation(tiny_data): +def test_dendrogram_creation(tiny_data): """Test dendrogram creation with a given path. """ @@ -87,7 +87,7 @@ def test_denrogram_creation(tiny_data): os.remove(output_path) -def test_denrogram_creation_with_file_object(tiny_data): +def test_dendrogram_creation_with_file_object(tiny_data): """Test dendrogram creation with a given file object. """ diff --git a/tests/corelay/test_base.py b/tests/corelay/test_base.py index b3fb866..ab61af4 100644 --- a/tests/corelay/test_base.py +++ b/tests/corelay/test_base.py @@ -7,8 +7,8 @@ class TestParam: """Test class for Param""" @staticmethod - def test_instatiation(): - """Param should instatiate correctly when passing any dtype.""" + def test_instantiation(): + """Param should instantiate correctly when passing any dtype.""" Param(object) @staticmethod diff --git a/tests/corelay/test_plugboard.py b/tests/corelay/test_plugboard.py index 6569d26..4ec881b 100644 --- a/tests/corelay/test_plugboard.py +++ b/tests/corelay/test_plugboard.py @@ -8,29 +8,29 @@ class TestSlot: """Test class for Slot""" @staticmethod def test_init(): - """Slot should successfully instatiate in any case""" + """Slot should successfully instantiate in any case""" Slot() @staticmethod def test_init_consistent_args(): - """If arguments are consistent, Slot should successfully instatiate""" + """If arguments are consistent, Slot should successfully instantiate""" Slot(dtype=int, default=5) @staticmethod def test_init_inconsistent_args(): - """If arguments are inconsistent, Slot should raise TypeError when instatiating""" + """If arguments are inconsistent, Slot should raise TypeError when instantiating""" with pytest.raises(TypeError): Slot(dtype=str, default=5) @staticmethod def test_init_unknown_args(): - """When unknown arguments are passed, Slot should raise TypeError when instatiating""" + """When unknown arguments are passed, Slot should raise TypeError when instantiating""" with pytest.raises(TypeError): Slot(monkey='banana') @staticmethod def test_init_class_name(): - """When instatiated in a class, the __name__ parameter of Slot should be set accordingly""" + """When instantiated in a class, the __name__ parameter of Slot should be set accordingly""" class SlotHolder: """Holds a single Slot""" my_slot = Slot() @@ -38,7 +38,7 @@ class SlotHolder: @staticmethod def test_init_instance_default(): - """When instatiated in a class and accessed in an instance, with only default set, the default value should be + """When instantiated in a class and accessed in an instance, with only default set, the default value should be returned. """ class SlotHolder: @@ -59,7 +59,7 @@ class SlotHolder: @staticmethod def test_instance_get_no_default(): - """When instatiated in a class and accessed in an instance, without anything set, accessing the value should + """When instantiated in a class and accessed in an instance, without anything set, accessing the value should raise a TypeError. """ class SlotHolder: @@ -173,114 +173,114 @@ class TestPlug: """Test class for Plug""" @staticmethod def test_init_with_slot_default(): - """Instatiating a Plug with a slot's default value should succeed.""" + """instantiating a Plug with a slot's default value should succeed.""" slot = Slot(dtype=int, default=10) Plug(slot) @staticmethod def test_init_no_slot_default(): - """Instatiating a Plug without a slot's default value should fail.""" + """instantiating a Plug without a slot's default value should fail.""" slot = Slot(dtype=int) with pytest.raises(TypeError): Plug(slot) @staticmethod def test_init_consistent(): - """Instatiating a Plug with obj and default set as the correct slot's dtype should succeed.""" + """instantiating a Plug with obj and default set as the correct slot's dtype should succeed.""" slot = Slot(dtype=int) Plug(slot, obj=15, default=16) @staticmethod def test_init_consistent_obj(): - """Instatiating a Plug with obj set as the correct slot's dtype should succeed.""" + """instantiating a Plug with obj set as the correct slot's dtype should succeed.""" slot = Slot(dtype=int) Plug(slot, obj=15) @staticmethod def test_init_consistent_default(): - """Instatiating a Plug with default set as the correct slot's dtype should succeed.""" + """instantiating a Plug with default set as the correct slot's dtype should succeed.""" slot = Slot(dtype=int) Plug(slot, default=15) @staticmethod def test_init_inconsistent_obj(): - """Instatiating a Plug with obj not set as slot's dtype should fail.""" + """instantiating a Plug with obj not set as slot's dtype should fail.""" slot = Slot(dtype=str) with pytest.raises(TypeError): Plug(slot, obj=15) @staticmethod def test_init_inconsistent_default(): - """Instatiating a Plug with obj not set as slot's dtype should fail.""" + """instantiating a Plug with obj not set as slot's dtype should fail.""" slot = Slot(dtype=str) with pytest.raises(TypeError): Plug(slot, default=15) @staticmethod - def test_obj_hierachy_obj(): + def test_obj_hierarchy_obj(): """Accessing obj with slot default, plug default and obj set should return obj.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot, obj='obj', default='default') assert plug.obj == 'obj' @staticmethod - def test_obj_hierachy_default(): + def test_obj_hierarchy_default(): """Accessing obj with slot default and plug default set should return default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') assert plug.obj == 'default' @staticmethod - def test_obj_hierachy_fallback(): + def test_obj_hierarchy_fallback(): """Accessing obj with only slot default set should return slot default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot) assert plug.obj == 'fallback' @staticmethod - def test_default_hierachy_obj(): + def test_default_hierarchy_obj(): """Accessing default with slot default, default and obj set should return default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') assert plug.default == 'default' @staticmethod - def test_default_hierachy_default(): + def test_default_hierarchy_default(): """Accessing default with slot default and plug default set should return default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') assert plug.default == 'default' @staticmethod - def test_default_hierachy_fallback(): + def test_default_hierarchy_fallback(): """Accessing default with only slot default set should return slot default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot) assert plug.default == 'fallback' @staticmethod - def test_fallback_hierachy_obj(): + def test_fallback_hierarchy_obj(): """Accessing fallback with slot default, default and obj set should return slot default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') assert plug.fallback == 'fallback' @staticmethod - def test_fallback_hierachy_default(): + def test_fallback_hierarchy_default(): """Accessing fallback with slot default and plug default set should return slot default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') assert plug.fallback == 'fallback' @staticmethod - def test_fallback_hierachy_fallback(): + def test_fallback_hierarchy_fallback(): """Accessing fallback with only slot default set should return slot default.""" slot = Slot(dtype=str, default='fallback') plug = Plug(slot) assert plug.fallback == 'fallback' @staticmethod - def test_hierachy_none(): + def test_hierarchy_none(): """Accessing any of default and fallback with only obj set should return None.""" slot = Slot(dtype=str) plug = Plug(slot, obj='obj') @@ -288,7 +288,7 @@ def test_hierachy_none(): assert plug.fallback is None @staticmethod - def test_delete_hierachy(): + def test_delete_hierarchy(): """Deleting obj with default set should return default.""" slot = Slot(dtype=str) plug = Plug(slot, default='default', obj='obj') @@ -296,7 +296,7 @@ def test_delete_hierachy(): assert plug.obj == 'default' @staticmethod - def test_delete_hierachy_last(): + def test_delete_hierarchy_last(): """Deleting such that all obj, default and fallback are None should fail.""" slot = Slot(dtype=str) plug = Plug(slot, default='default', obj='obj') @@ -392,25 +392,25 @@ class TestPlugboard: """Test class for Plugboard""" @staticmethod def test_init(): - """Instatiating a Plugboard without anything set should suceed.""" + """instantiating a Plugboard without anything set should succeed.""" Plugboard() @staticmethod def test_init_unknown_kwargs(): - """Instatiating a Plugboard with unknown kwargs should fail.""" + """instantiating a Plugboard with unknown kwargs should fail.""" with pytest.raises(TypeError): Plugboard(stuff=19) @staticmethod def test_init_args(): - """Instatiating a Plugboard with any positional args should fail.""" + """instantiating a Plugboard with any positional args should fail.""" with pytest.raises(TypeError): # pylint: disable=too-many-function-args Plugboard(19) @staticmethod def test_init_assign(): - """Instatiating a Plugboard with kwargs identifying Slots should set those.""" + """instantiating a Plugboard with kwargs identifying Slots should set those.""" class MyPlugboard(Plugboard): """Custom Plugboard""" my_slot = Slot(dtype=int, default=15) diff --git a/tests/corelay/test_tracker.py b/tests/corelay/test_tracker.py index 84e1cc9..0b73924 100644 --- a/tests/corelay/test_tracker.py +++ b/tests/corelay/test_tracker.py @@ -23,7 +23,7 @@ class SubTracked(Tracker): @pytest.fixture(scope='module') def values(): - """Fixute of list of values, how they were written in the tracked fixture class.""" + """Fixture of list of values, how they were written in the tracked fixture class.""" result = dict( attr_1=42, attr_2='apple', diff --git a/tests/cspell/.cspell.json b/tests/cspell/.cspell.json new file mode 100644 index 0000000..5727f3a --- /dev/null +++ b/tests/cspell/.cspell.json @@ -0,0 +1,212 @@ +{ + // Version of the settings file, this should always be 0.2 + "version": "0.2", + + // The code in this repository is only checked against American English + "language": "en-US", + + // Words do not have to match in case + "caseSensitive": false, + + // A list of file types that should be enabled (not all file types are enabled by default) + "enableFiletypes": [ + "bibtex", + "github-actions-workflow", + "gitignore", + "ini", + "toml" + ], + + // A list of paths that are ignored when spell-checking (ignores the .git directory, but excludes the commit message file that is created by Git + // during a commit, and the cSpell configuration file itself) + "ignorePaths": [ + "**/__pycache__", + "**/.cspell.json", + "**/.git/!(COMMIT_EDITMSG)", + "**/*.svg", + "**/docs/build", + "**/docs/doctree", + "**/node_modules", + "**/package-lock.json", + "**/uv.lock" + ], + + // A list of dictionaries that should be added beyond the American English dictionary + "dictionaries": [ + + // Dictionaries that contain common misspellings + "en-us", + "en-us-common-misspellings", + "en-common-misspellings", + + // Dictionaries that contain the keywords of the programming, markup, styling, and configuration languages used in the project + "bash", + "css", + "python-common", + "python", + + // Other dictionaries, that contain the names of well-known companies, common acronyms related to computing, words used in data science, + // common file extensions, common font names, common words often encountered in full-stack development, the popular blind text Lorem Ipsum, + // miscellaneous terms that are common in software projects, terms common in computer networking, terms used in popular public licenses, + // common software terms, and terms related to web services + "companies", + "computing-acronyms", + "data-science", + "filetypes", + "fonts", + "fullstack", + "lorem-ipsum", + "misc", + "networking-terms", + "npm", + "public-licenses", + "software-terms", + "web-services" + ], + + // A list of words to be always considered incorrect (by default, only words that have 4 or more characters are spell-checked, so this list + // includes some common typos of words that are too short to be recognizes by CSpell) + "flagWords": [ + "fo", + "fro", + "hte", + "ot", + "teh" + ], + + // A list of words that are not in the included default dictionary for American English + "words": [ + "absspectral", + "addopts", + "adfg", + "agglo", + "Anders", + "Anson", + "automodule", + "autosummary", + "basepython", + "bcde", + "bibfiles", + "Chormai", + "chr5tphr", + "chrstphr", + "clusterings", + "codezombiech", + "cooperativity", + "copybutton", + "CoRelAy", + "csints", + "datatemplates", + "DBSCAN", + "dcba", + "dendrogram", + "docstrings", + "doctree", + "docz", + "dtypes", + "edcba", + "eigenstuff", + "eigsh", + "eigval", + "eigvals", + "eigvec", + "Envlist", + "envname", + "envsitepackagesdir", + "exrc", + "extlinks", + "figsize", + "fullspectral", + "genindex", + "getfixturevalue", + "getrev", + "hashval", + "hdbscan", + "histogramdd", + "htmlcov", + "htmlvalidate", + "ifmat", + "imgmath", + "inlinevar", + "iobj", + "iterhash", + "iterread", + "iterwrite", + "keepdims", + "kmeans", + "Laplacians", + "Lapuschkin", + "lecode", + "lextudio", + "linalg", + "linestart", + "linestop", + "ller", + "mathjax", + "membertype", + "metacls", + "metrohash", + "mgrid", + "modindex", + "modname", + "modpath", + "moveaxis", + "msvs", + "Müller", + "mypackage", + "ndarray", + "ndarrays", + "Neumann", + "nosignatures", + "notest", + "nsamples", + "overgeneral", + "parseable", + "Pattarawat", + "pdist", + "pdistance", + "pearsonr", + "Pickler", + "Plugboards", + "posargs", + "prepreprocess", + "pybtex", + "pydoclint", + "pygtk", + "pylintrc", + "randn", + "rcfile", + "regionref", + "ritwickdey", + "rodolphebarbanneau", + "Samek", + "skimage", + "sphinxcontrib", + "Sprincl", + "sqeuclidean", + "squareform", + "SSIM", + "submod", + "subname", + "subnames", + "symmetrix", + "tamasfe", + "testpaths", + "toctree", + "todense", + "topmodulename", + "toxinidir", + "toxworkdir", + "trange", + "TSNE", + "umap", + "vimrc", + "vsicons", + "wayou", + "Wojciech", + "xticks", + "yticks", + "yzhang", + "Zennit" + ] +} diff --git a/tests/cspell/package-lock.json b/tests/cspell/package-lock.json new file mode 100644 index 0000000..fb4e777 --- /dev/null +++ b/tests/cspell/package-lock.json @@ -0,0 +1,1166 @@ +{ + "name": "@virelay/cspell-config", + "version": "0.5.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@virelay/cspell-config", + "version": "0.5.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "cspell": "^8.16.1" + } + }, + "node_modules/@cspell/cspell-bundled-dicts": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.16.1.tgz", + "integrity": "sha512-EkbtoYpmiN9YPfcOoPcMnIrJBZh13mun64jPyyaYhrPPToiU5+CisZ7ZKUBGnqNaatuciMUxwIudhanQJ7Yhnw==", + "license": "MIT", + "dependencies": { + "@cspell/dict-ada": "^4.0.5", + "@cspell/dict-al": "^1.0.3", + "@cspell/dict-aws": "^4.0.7", + "@cspell/dict-bash": "^4.1.8", + "@cspell/dict-companies": "^3.1.7", + "@cspell/dict-cpp": "^6.0.2", + "@cspell/dict-cryptocurrencies": "^5.0.3", + "@cspell/dict-csharp": "^4.0.5", + "@cspell/dict-css": "^4.0.16", + "@cspell/dict-dart": "^2.2.4", + "@cspell/dict-django": "^4.1.3", + "@cspell/dict-docker": "^1.1.11", + "@cspell/dict-dotnet": "^5.0.8", + "@cspell/dict-elixir": "^4.0.6", + "@cspell/dict-en_us": "^4.3.28", + "@cspell/dict-en-common-misspellings": "^2.0.7", + "@cspell/dict-en-gb": "1.1.33", + "@cspell/dict-filetypes": "^3.0.8", + "@cspell/dict-flutter": "^1.0.3", + "@cspell/dict-fonts": "^4.0.3", + "@cspell/dict-fsharp": "^1.0.4", + "@cspell/dict-fullstack": "^3.2.3", + "@cspell/dict-gaming-terms": "^1.0.8", + "@cspell/dict-git": "^3.0.3", + "@cspell/dict-golang": "^6.0.17", + "@cspell/dict-google": "^1.0.4", + "@cspell/dict-haskell": "^4.0.4", + "@cspell/dict-html": "^4.0.10", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-java": "^5.0.10", + "@cspell/dict-julia": "^1.0.4", + "@cspell/dict-k8s": "^1.0.9", + "@cspell/dict-latex": "^4.0.3", + "@cspell/dict-lorem-ipsum": "^4.0.3", + "@cspell/dict-lua": "^4.0.6", + "@cspell/dict-makefile": "^1.0.3", + "@cspell/dict-markdown": "^2.0.7", + "@cspell/dict-monkeyc": "^1.0.9", + "@cspell/dict-node": "^5.0.5", + "@cspell/dict-npm": "^5.1.14", + "@cspell/dict-php": "^4.0.13", + "@cspell/dict-powershell": "^5.0.13", + "@cspell/dict-public-licenses": "^2.0.11", + "@cspell/dict-python": "^4.2.12", + "@cspell/dict-r": "^2.0.4", + "@cspell/dict-ruby": "^5.0.7", + "@cspell/dict-rust": "^4.0.10", + "@cspell/dict-scala": "^5.0.6", + "@cspell/dict-software-terms": "^4.1.17", + "@cspell/dict-sql": "^2.1.8", + "@cspell/dict-svelte": "^1.0.5", + "@cspell/dict-swift": "^2.0.4", + "@cspell/dict-terraform": "^1.0.6", + "@cspell/dict-typescript": "^3.1.11", + "@cspell/dict-vue": "^3.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-json-reporter": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.16.1.tgz", + "integrity": "sha512-ue1paJ2OE2BjIBQHXFMHnFqJL5xMrE/TLveOntDSCKJw7edCGP4XJA6Q0ZfUgR/ZAP3SYKNPkajEWbDTMfG+XA==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.16.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-pipe": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.16.1.tgz", + "integrity": "sha512-6N+QZ3y65JRgGrQhZHmaBHESR+nC0J8nySGaYKclit8yk3jLZ/ORw9aoSGIj+dMPzImkNEDh+C1B1zdV4X8W6A==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-resolver": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.16.1.tgz", + "integrity": "sha512-CfVI2JFMwh9/n1QuU9niEONbYCX1XGKqmyCcHQUzAapSqGzbAmFrRFnvyKwNL+mmy1bxli9EZV8f5vBco26f9Q==", + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-service-bus": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.16.1.tgz", + "integrity": "sha512-URaralJKcdHZH/Lr25L28GJo2Ub07adHPPhOL83BvmPyGkboehmz5arjNrgQFwS+IvGjHLdp5uzEJd0xyeHGdw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-types": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.16.1.tgz", + "integrity": "sha512-B8bHlBaDSMDMEq++H8qO9osKUkzWUrP4CgWQyRqlXZ9EOdnJ469Tp1wghcQ7DezII3aXYrHiVKsUYY9VvjkhIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/dict-ada": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.0.5.tgz", + "integrity": "sha512-6/RtZ/a+lhFVmrx/B7bfP7rzC4yjEYe8o74EybXcvu4Oue6J4Ey2WSYj96iuodloj1LWrkNCQyX5h4Pmcj0Iag==", + "license": "MIT" + }, + "node_modules/@cspell/dict-al": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.0.3.tgz", + "integrity": "sha512-V1HClwlfU/qwSq2Kt+MkqRAsonNu3mxjSCDyGRecdLGIHmh7yeEeaxqRiO/VZ4KP+eVSiSIlbwrb5YNFfxYZbw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-aws": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.7.tgz", + "integrity": "sha512-PoaPpa2NXtSkhGIMIKhsJUXB6UbtTt6Ao3x9JdU9kn7fRZkwD4RjHDGqulucIOz7KeEX/dNRafap6oK9xHe4RA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-bash": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.1.8.tgz", + "integrity": "sha512-I2CM2pTNthQwW069lKcrVxchJGMVQBzru2ygsHCwgidXRnJL/NTjAPOFTxN58Jc1bf7THWghfEDyKX/oyfc0yg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-companies": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.1.7.tgz", + "integrity": "sha512-ncVs/efuAkP1/tLDhWbXukBjgZ5xOUfe03neHMWsE8zvXXc5+Lw6TX5jaJXZLOoES/f4j4AhRE20jsPCF5pm+A==", + "license": "MIT" + }, + "node_modules/@cspell/dict-cpp": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.2.tgz", + "integrity": "sha512-yw5eejWvY4bAnc6LUA44m4WsFwlmgPt2uMSnO7QViGMBDuoeopMma4z9XYvs4lSjTi8fIJs/A1YDfM9AVzb8eg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-cryptocurrencies": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.3.tgz", + "integrity": "sha512-bl5q+Mk+T3xOZ12+FG37dB30GDxStza49Rmoax95n37MTLksk9wBo1ICOlPJ6PnDUSyeuv4SIVKgRKMKkJJglA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-csharp": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.5.tgz", + "integrity": "sha512-c/sFnNgtRwRJxtC3JHKkyOm+U3/sUrltFeNwml9VsxKBHVmvlg4tk4ar58PdpW9/zTlGUkWi2i85//DN1EsUCA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-css": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.16.tgz", + "integrity": "sha512-70qu7L9z/JR6QLyJPk38fNTKitlIHnfunx0wjpWQUQ8/jGADIhMCrz6hInBjqPNdtGpYm8d1dNFyF8taEkOgrQ==", + "license": "MIT" + }, + "node_modules/@cspell/dict-dart": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.2.4.tgz", + "integrity": "sha512-of/cVuUIZZK/+iqefGln8G3bVpfyN6ZtH+LyLkHMoR5tEj+2vtilGNk9ngwyR8L4lEqbKuzSkOxgfVjsXf5PsQ==", + "license": "MIT" + }, + "node_modules/@cspell/dict-data-science": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.5.tgz", + "integrity": "sha512-nNSILXmhSJox9/QoXICPQgm8q5PbiSQP4afpbkBqPi/u/b3K9MbNH5HvOOa6230gxcGdbZ9Argl2hY/U8siBlg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-django": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.3.tgz", + "integrity": "sha512-yBspeL3roJlO0a1vKKNaWABURuHdHZ9b1L8d3AukX0AsBy9snSggc8xCavPmSzNfeMDXbH+1lgQiYBd3IW03fg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-docker": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.11.tgz", + "integrity": "sha512-s0Yhb16/R+UT1y727ekbR/itWQF3Qz275DR1ahOa66wYtPjHUXmhM3B/LT3aPaX+hD6AWmK23v57SuyfYHUjsw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-dotnet": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.8.tgz", + "integrity": "sha512-MD8CmMgMEdJAIPl2Py3iqrx3B708MbCIXAuOeZ0Mzzb8YmLmiisY7QEYSZPg08D7xuwARycP0Ki+bb0GAkFSqg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-elixir": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.6.tgz", + "integrity": "sha512-TfqSTxMHZ2jhiqnXlVKM0bUADtCvwKQv2XZL/DI0rx3doG8mEMS8SGPOmiyyGkHpR/pGOq18AFH3BEm4lViHIw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-en_us": { + "version": "4.3.28", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.28.tgz", + "integrity": "sha512-BN1PME7cOl7DXRQJ92pEd1f0Xk5sqjcDfThDGkKcsgwbSOY7KnTc/czBW6Pr3WXIchIm6cT12KEfjNqx7U7Rrw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-en-common-misspellings": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.0.7.tgz", + "integrity": "sha512-qNFo3G4wyabcwnM+hDrMYKN9vNVg/k9QkhqSlSst6pULjdvPyPs1mqz1689xO/v9t8e6sR4IKc3CgUXDMTYOpA==", + "license": "CC BY-SA 4.0" + }, + "node_modules/@cspell/dict-en-gb": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", + "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", + "license": "MIT" + }, + "node_modules/@cspell/dict-filetypes": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.8.tgz", + "integrity": "sha512-D3N8sm/iptzfVwsib/jvpX+K/++rM8SRpLDFUaM4jxm8EyGmSIYRbKZvdIv5BkAWmMlTWoRqlLn7Yb1b11jKJg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-flutter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.0.3.tgz", + "integrity": "sha512-52C9aUEU22ptpgYh6gQyIdA4MP6NPwzbEqndfgPh3Sra191/kgs7CVqXiO1qbtZa9gnYHUoVApkoxRE7mrXHfg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-fonts": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.3.tgz", + "integrity": "sha512-sPd17kV5qgYXLteuHFPn5mbp/oCHKgitNfsZLFC3W2fWEgZlhg4hK+UGig3KzrYhhvQ8wBnmZrAQm0TFKCKzsA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-fsharp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.0.4.tgz", + "integrity": "sha512-G5wk0o1qyHUNi9nVgdE1h5wl5ylq7pcBjX8vhjHcO4XBq20D5eMoXjwqMo/+szKAqzJ+WV3BgAL50akLKrT9Rw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-fullstack": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.3.tgz", + "integrity": "sha512-62PbndIyQPH11mAv0PyiyT0vbwD0AXEocPpHlCHzfb5v9SspzCCbzQ/LIBiFmyRa+q5LMW35CnSVu6OXdT+LKg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-gaming-terms": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.0.8.tgz", + "integrity": "sha512-7OL0zTl93WFWhhtpXFrtm9uZXItC3ncAs8d0iQDMMFVNU1rBr6raBNxJskxE5wx2Ant12fgI66ZGVagXfN+yfA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-git": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.3.tgz", + "integrity": "sha512-LSxB+psZ0qoj83GkyjeEH/ZViyVsGEF/A6BAo8Nqc0w0HjD2qX/QR4sfA6JHUgQ3Yi/ccxdK7xNIo67L2ScW5A==", + "license": "MIT" + }, + "node_modules/@cspell/dict-golang": { + "version": "6.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.17.tgz", + "integrity": "sha512-uDDLEJ/cHdLiqPw4+5BnmIo2i/TSR+uDvYd6JlBjTmjBKpOCyvUgYRztH7nv5e7virsN5WDiUWah4/ATQGz4Pw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-google": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.4.tgz", + "integrity": "sha512-JThUT9eiguCja1mHHLwYESgxkhk17Gv7P3b1S7ZJzXw86QyVHPrbpVoMpozHk0C9o+Ym764B7gZGKmw9uMGduQ==", + "license": "MIT" + }, + "node_modules/@cspell/dict-haskell": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.4.tgz", + "integrity": "sha512-EwQsedEEnND/vY6tqRfg9y7tsnZdxNqOxLXSXTsFA6JRhUlr8Qs88iUUAfsUzWc4nNmmzQH2UbtT25ooG9x4nA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-html": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.10.tgz", + "integrity": "sha512-I9uRAcdtHbh0wEtYZlgF0TTcgH0xaw1B54G2CW+tx4vHUwlde/+JBOfIzird4+WcMv4smZOfw+qHf7puFUbI5g==", + "license": "MIT" + }, + "node_modules/@cspell/dict-html-symbol-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.3.tgz", + "integrity": "sha512-aABXX7dMLNFdSE8aY844X4+hvfK7977sOWgZXo4MTGAmOzR8524fjbJPswIBK7GaD3+SgFZ2yP2o0CFvXDGF+A==", + "license": "MIT" + }, + "node_modules/@cspell/dict-java": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.10.tgz", + "integrity": "sha512-pVNcOnmoGiNL8GSVq4WbX/Vs2FGS0Nej+1aEeGuUY9CU14X8yAVCG+oih5ZoLt1jaR8YfR8byUF8wdp4qG4XIw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-julia": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.0.4.tgz", + "integrity": "sha512-bFVgNX35MD3kZRbXbJVzdnN7OuEqmQXGpdOi9jzB40TSgBTlJWA4nxeAKV4CPCZxNRUGnLH0p05T/AD7Aom9/w==", + "license": "MIT" + }, + "node_modules/@cspell/dict-k8s": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.9.tgz", + "integrity": "sha512-Q7GELSQIzo+BERl2ya/nBEnZeQC+zJP19SN1pI6gqDYraM51uYJacbbcWLYYO2Y+5joDjNt/sd/lJtLaQwoSlA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-latex": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.3.tgz", + "integrity": "sha512-2KXBt9fSpymYHxHfvhUpjUFyzrmN4c4P8mwIzweLyvqntBT3k0YGZJSriOdjfUjwSygrfEwiuPI1EMrvgrOMJw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-lorem-ipsum": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.3.tgz", + "integrity": "sha512-WFpDi/PDYHXft6p0eCXuYnn7mzMEQLVeqpO+wHSUd+kz5ADusZ4cpslAA4wUZJstF1/1kMCQCZM6HLZic9bT8A==", + "license": "MIT" + }, + "node_modules/@cspell/dict-lua": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.6.tgz", + "integrity": "sha512-Jwvh1jmAd9b+SP9e1GkS2ACbqKKRo9E1f9GdjF/ijmooZuHU0hPyqvnhZzUAxO1egbnNjxS/J2T6iUtjAUK2KQ==", + "license": "MIT" + }, + "node_modules/@cspell/dict-makefile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.3.tgz", + "integrity": "sha512-R3U0DSpvTs6qdqfyBATnePj9Q/pypkje0Nj26mQJ8TOBQutCRAJbr2ZFAeDjgRx5EAJU/+8txiyVF97fbVRViw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-markdown": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.7.tgz", + "integrity": "sha512-F9SGsSOokFn976DV4u/1eL4FtKQDSgJHSZ3+haPRU5ki6OEqojxKa8hhj4AUrtNFpmBaJx/WJ4YaEzWqG7hgqg==", + "license": "MIT", + "peerDependencies": { + "@cspell/dict-css": "^4.0.16", + "@cspell/dict-html": "^4.0.10", + "@cspell/dict-html-symbol-entities": "^4.0.3", + "@cspell/dict-typescript": "^3.1.11" + } + }, + "node_modules/@cspell/dict-monkeyc": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.9.tgz", + "integrity": "sha512-Jvf6g5xlB4+za3ThvenYKREXTEgzx5gMUSzrAxIiPleVG4hmRb/GBSoSjtkGaibN3XxGx5x809gSTYCA/IHCpA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-node": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.5.tgz", + "integrity": "sha512-7NbCS2E8ZZRZwlLrh2sA0vAk9n1kcTUiRp/Nia8YvKaItGXLfxYqD2rMQ3HpB1kEutal6hQLVic3N2Yi1X7AaA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-npm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.1.14.tgz", + "integrity": "sha512-7VV/rrRlxOwy5j0bpw6/Uci+nx/rwSgx45FJdeKq++nHsBx/nEXMFNODknm4Mi6i7t7uOVHExpifrR6w6xTWww==", + "license": "MIT" + }, + "node_modules/@cspell/dict-php": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.13.tgz", + "integrity": "sha512-P6sREMZkhElzz/HhXAjahnICYIqB/HSGp1EhZh+Y6IhvC15AzgtDP8B8VYCIsQof6rPF1SQrFwunxOv8H1e2eg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-powershell": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.13.tgz", + "integrity": "sha512-0qdj0XZIPmb77nRTynKidRJKTU0Fl+10jyLbAhFTuBWKMypVY06EaYFnwhsgsws/7nNX8MTEQuewbl9bWFAbsg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-public-licenses": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.11.tgz", + "integrity": "sha512-rR5KjRUSnVKdfs5G+gJ4oIvQvm8+NJ6cHWY2N+GE69/FSGWDOPHxulCzeGnQU/c6WWZMSimG9o49i9r//lUQyA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-python": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.12.tgz", + "integrity": "sha512-U25eOFu+RE0aEcF2AsxZmq3Lic7y9zspJ9SzjrC0mfJz+yr3YmSCw4E0blMD3mZoNcf7H/vMshuKIY5AY36U+Q==", + "license": "MIT", + "dependencies": { + "@cspell/dict-data-science": "^2.0.5" + } + }, + "node_modules/@cspell/dict-r": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.0.4.tgz", + "integrity": "sha512-cBpRsE/U0d9BRhiNRMLMH1PpWgw+N+1A2jumgt1if9nBGmQw4MUpg2u9I0xlFVhstTIdzXiLXMxP45cABuiUeQ==", + "license": "MIT" + }, + "node_modules/@cspell/dict-ruby": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.7.tgz", + "integrity": "sha512-4/d0hcoPzi5Alk0FmcyqlzFW9lQnZh9j07MJzPcyVO62nYJJAGKaPZL2o4qHeCS/od/ctJC5AHRdoUm0ktsw6Q==", + "license": "MIT" + }, + "node_modules/@cspell/dict-rust": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.10.tgz", + "integrity": "sha512-6o5C8566VGTTctgcwfF3Iy7314W0oMlFFSQOadQ0OEdJ9Z9ERX/PDimrzP3LGuOrvhtEFoK8pj+BLnunNwRNrw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-scala": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.6.tgz", + "integrity": "sha512-tl0YWAfjUVb4LyyE4JIMVE8DlLzb1ecHRmIWc4eT6nkyDqQgHKzdHsnusxFEFMVLIQomgSg0Zz6hJ5S1E4W4ww==", + "license": "MIT" + }, + "node_modules/@cspell/dict-software-terms": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-4.1.17.tgz", + "integrity": "sha512-QORIk1R5DV8oOQ+oAlUWE7UomaJwUucqu2srrc2+PmkoI6R1fJwwg2uHCPBWlIb4PGDNEdXLv9BAD13H+0wytQ==", + "license": "MIT" + }, + "node_modules/@cspell/dict-sql": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.1.8.tgz", + "integrity": "sha512-dJRE4JV1qmXTbbGm6WIcg1knmR6K5RXnQxF4XHs5HA3LAjc/zf77F95i5LC+guOGppVF6Hdl66S2UyxT+SAF3A==", + "license": "MIT" + }, + "node_modules/@cspell/dict-svelte": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.5.tgz", + "integrity": "sha512-sseHlcXOqWE4Ner9sg8KsjxwSJ2yssoJNqFHR9liWVbDV+m7kBiUtn2EB690TihzVsEmDr/0Yxrbb5Bniz70mA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-swift": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.4.tgz", + "integrity": "sha512-CsFF0IFAbRtYNg0yZcdaYbADF5F3DsM8C4wHnZefQy8YcHP/qjAF/GdGfBFBLx+XSthYuBlo2b2XQVdz3cJZBw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-terraform": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.0.6.tgz", + "integrity": "sha512-Sqm5vGbXuI9hCFcr4w6xWf4Y25J9SdleE/IqfM6RySPnk8lISEmVdax4k6+Kinv9qaxyvnIbUUN4WFLWcBPQAg==", + "license": "MIT" + }, + "node_modules/@cspell/dict-typescript": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.1.11.tgz", + "integrity": "sha512-FwvK5sKbwrVpdw0e9+1lVTl8FPoHYvfHRuQRQz2Ql5XkC0gwPPkpoyD1zYImjIyZRoYXk3yp9j8ss4iz7A7zoQ==", + "license": "MIT" + }, + "node_modules/@cspell/dict-vue": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.3.tgz", + "integrity": "sha512-akmYbrgAGumqk1xXALtDJcEcOMYBYMnkjpmGzH13Ozhq1mkPF4VgllFQlm1xYde+BUKNnzMgPEzxrL2qZllgYA==", + "license": "MIT" + }, + "node_modules/@cspell/dynamic-import": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.16.1.tgz", + "integrity": "sha512-mEfdeS1kFKpJoDsQ8wW6PxO3+ncYuZCWCASR0trbzZDduzO2RcogMUgzP99obHtYbgXadw94qcQWXB8OYTPSwg==", + "license": "MIT", + "dependencies": { + "import-meta-resolve": "^4.1.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@cspell/filetypes": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-8.16.1.tgz", + "integrity": "sha512-zpbNg3n26muR1jdMbylw5YsaVGyS9LU5Lfy20gU7RygAk6kFyx3Yz4C84EihBGQHy2gVEsEeyCCxk+R8RXuPZA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/strong-weak-map": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.16.1.tgz", + "integrity": "sha512-jJQS05wg2iUkLKnPR8NEq3LqvqHWKnvUDFoPwaJzYw6ol/O4yi/lv+Me9+XCPrgjpnAz+8APhWkhrR/O71R1Bw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/url": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-8.16.1.tgz", + "integrity": "sha512-kGlr7Wdo4xJpXKal/Gqo3Ll5Is7ptlIlLZOB/hzR6R53Fw4N6SdipTDIeHHqC15p2AXTEG6TSNdhk9dA50LY6w==", + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/clear-module": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", + "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "license": "MIT", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cspell": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.16.1.tgz", + "integrity": "sha512-ILuCjnY3JPY2oO62PodTQD6n3DGTKTwB+IU1tE9EC6EP2Xw6z3Ir+hO2DO6QlRUmZlGrkGMek5U06nNmztt4eA==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-json-reporter": "8.16.1", + "@cspell/cspell-pipe": "8.16.1", + "@cspell/cspell-types": "8.16.1", + "@cspell/dynamic-import": "8.16.1", + "@cspell/url": "8.16.1", + "chalk": "^5.3.0", + "chalk-template": "^1.1.0", + "commander": "^12.1.0", + "cspell-dictionary": "8.16.1", + "cspell-gitignore": "8.16.1", + "cspell-glob": "8.16.1", + "cspell-io": "8.16.1", + "cspell-lib": "8.16.1", + "fast-json-stable-stringify": "^2.1.0", + "file-entry-cache": "^9.1.0", + "get-stdin": "^9.0.0", + "semver": "^7.6.3", + "tinyglobby": "^0.2.10" + }, + "bin": { + "cspell": "bin.mjs", + "cspell-esm": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" + } + }, + "node_modules/cspell-config-lib": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.16.1.tgz", + "integrity": "sha512-ohbSi9sI14rMdFc2g17ogObGGkd/x6zUVOzCH1nEOefC9yJYYfsvaMHqdhk0rOjvmF95j5OK4dm5oid+DKQcpw==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.16.1", + "comment-json": "^4.2.5", + "yaml": "^2.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-dictionary": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.16.1.tgz", + "integrity": "sha512-NL/vwf5SjtkWWaEUh+0dogKdEU4UuepJaNh36FX8W1CFtQXj7yEs45x4K7/Fp+pn/4AT7Qe7WpSSWi9z5GcqKg==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.16.1", + "@cspell/cspell-types": "8.16.1", + "cspell-trie-lib": "8.16.1", + "fast-equals": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-gitignore": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.16.1.tgz", + "integrity": "sha512-Gg8qvFc8wr1D7TvB+GSfT1jyrUoUmPiG3WdOnQnxOSYKJesOiVvNxLv7YXRFkcUKG1VU6XDUkpb/uzKh3k2rKw==", + "license": "MIT", + "dependencies": { + "@cspell/url": "8.16.1", + "cspell-glob": "8.16.1", + "cspell-io": "8.16.1", + "find-up-simple": "^1.0.0" + }, + "bin": { + "cspell-gitignore": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-glob": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.16.1.tgz", + "integrity": "sha512-EukaXFaUrgrY9G4bB2PguzpkAoOq6ai9acLl6gWD+6DgVEwkLqPmCWjsFJA0MaqVp9QvPsIfCy4KCnx35csG/g==", + "license": "MIT", + "dependencies": { + "@cspell/url": "8.16.1", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-grammar": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.16.1.tgz", + "integrity": "sha512-7IRYa0O1xfK2HVbhGSpOPPt5HlP2ZHRHtdLU2iOvMSCkh0cSPERu++kdprvcaOf7E7koo0P+bxHSprcYbU/agg==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.16.1", + "@cspell/cspell-types": "8.16.1" + }, + "bin": { + "cspell-grammar": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-io": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.16.1.tgz", + "integrity": "sha512-25MOQfy7EhdVeoNUW/+jyb5ArDYSLbaFwVToakHtLGuYk9cW8q8MAHq1W9GzW06wXswT2sQsRvaozmIOTDIOnw==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-service-bus": "8.16.1", + "@cspell/url": "8.16.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-lib": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.16.1.tgz", + "integrity": "sha512-Gn1vJcyhYe78iB+9dms8rnfgDEfJgYocXapFPTOcZV3EUWKcV4wyCiHdbK3j2ElLXmPuSPg4eZSlxxk8ITD0Aw==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "8.16.1", + "@cspell/cspell-pipe": "8.16.1", + "@cspell/cspell-resolver": "8.16.1", + "@cspell/cspell-types": "8.16.1", + "@cspell/dynamic-import": "8.16.1", + "@cspell/filetypes": "8.16.1", + "@cspell/strong-weak-map": "8.16.1", + "@cspell/url": "8.16.1", + "clear-module": "^4.1.2", + "comment-json": "^4.2.5", + "cspell-config-lib": "8.16.1", + "cspell-dictionary": "8.16.1", + "cspell-glob": "8.16.1", + "cspell-grammar": "8.16.1", + "cspell-io": "8.16.1", + "cspell-trie-lib": "8.16.1", + "env-paths": "^3.0.0", + "fast-equals": "^5.0.1", + "gensequence": "^7.0.0", + "import-fresh": "^3.3.0", + "resolve-from": "^5.0.0", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-uri": "^3.0.8", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-trie-lib": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.16.1.tgz", + "integrity": "sha512-T86nszsjQjyZ35dOWk7qN17Hem0cVeXJ4D1v/gIG+Y0Umo7dBW7AwmTvUy8iMFAra29cSdgRH+yk6q1qdpA+ZA==", + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.16.1", + "@cspell/cspell-types": "8.16.1", + "gensequence": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", + "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==", + "license": "MIT", + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", + "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "license": "MIT", + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "license": "ISC" + }, + "node_modules/gensequence": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", + "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/tests/cspell/package.json b/tests/cspell/package.json new file mode 100644 index 0000000..ea771f1 --- /dev/null +++ b/tests/cspell/package.json @@ -0,0 +1,17 @@ +{ + "name": "@corelay/cspell-config", + "version": "0.5.0", + "description": "The CSpell configuration for CoRelAy.", + "author": { + "name": "David Neumann", + "email": "david.neumann@lecode.de" + }, + "license": "AGPL-3.0-or-later", + "private": true, + "dependencies": { + "cspell": "^8.16.1" + }, + "scripts": { + "cspell": "npx --prefix tests/cspell cspell lint --config .cspell.json --root ../.. '**'" + } +} From debfcbf429b2b35ad1220115b8b922c11c6bedae Mon Sep 17 00:00:00 2001 From: David Neumann Date: Wed, 16 Apr 2025 11:36:47 +0200 Subject: [PATCH 03/19] Renamed the "master" Branch to "main" The "master" branch was renamed to "main". All references to the "master" branch in the repository have been updated to point to "main". --- .github/workflows/tests.yml | 4 ++-- CHANGELOG.md | 10 ++++++---- README.md | 2 +- docs/source/conf.py | 2 +- src/corelay/io/hashing.py | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22264c1..4dd3d87 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,9 +1,9 @@ name: tests on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] jobs: test: diff --git a/CHANGELOG.md b/CHANGELOG.md index 27c2df1..2dafcfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ *Release date to be determined.* -### General (v0.3.0) +### General Updates in v0.3.0 +- Renamed the `master` branch to `main` in order to avoid any links to sensitive topics. + - All references to the `master` branch in the repository were updated to `main`. - Added this changelog, as well as a contributors list, which contains a list of all people that made contributions to the project. - Added a CSpell configuration for spell-checking the contents of the repository, checked all files, and corrected all spelling mistakes. @@ -13,13 +15,13 @@ *Released on June 21, 2022.* -### General (v0.2.1) +### General Updates in v0.2.1 - Fixed a small typo in the read me by @p16i in #8. - Clarified the maximum number of neighbors in the `SparseKNN` affinity by replacing the confusing `if`-`else` expression with the `min` function by @chr5tphr in #10. - Added a new logo, which has a design similar to the logo of ViRelAy and added a link to the documentation, as well as some badges to the read me by @chr5tphr in #12. -### CI/CD (v0.2.1) +### CI/CD Updates in v0.2.1 - Added a GitHub Actions workflow, which runs the unit tests and builds the documentation by @chr5tphr in #13. - Fixed tests and PyLint errors @@ -37,7 +39,7 @@ - Change the linkcode URL in the documentation to correctly point to `src/corelay` on GitHub. - Updated the outdated PyLint configuration by removing deprecated disables. -### Documentation (v0.2.1) +### Documentation Updates in v0.2.1 - Added Sphinx-based documentation by @chr5tphr in #11. - Fixed the Docstrings and some NumPyDoc issues. diff --git a/README.md b/README.md index 45e7122..efff1b8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Documentation Status](https://readthedocs.org/projects/corelay/badge/?version=latest)](https://corelay.readthedocs.io/en/latest/?badge=latest) [![tests](https://github.com/virelay/corelay/actions/workflows/tests.yml/badge.svg)](https://github.com/virelay/corelay/actions/workflows/tests.yml) [![PyPI Version](https://img.shields.io/pypi/v/corelay)](https://pypi.org/project/corelay/) -[![License](https://img.shields.io/pypi/l/corelay)](https://github.com/virelay/corelay/blob/master/COPYING.LESSER) +[![License](https://img.shields.io/pypi/l/corelay)](https://github.com/virelay/corelay/blob/main/COPYING.LESSER) CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (Task) with default operations (Processor). diff --git a/docs/source/conf.py b/docs/source/conf.py index 6ad8262..c81d13a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -85,7 +85,7 @@ def getrev(): text=True ).stdout[:-1] except CalledProcessError: - revision = 'master' + revision = 'main' return revision diff --git a/src/corelay/io/hashing.py b/src/corelay/io/hashing.py index 58e6b2d..61bc5c1 100644 --- a/src/corelay/io/hashing.py +++ b/src/corelay/io/hashing.py @@ -2,7 +2,7 @@ Note ---- -See https://github.com/chr5tphr/funcache/blob/master/funcache/hashing.py +See https://github.com/chr5tphr/funcache/blob/main/funcache/hashing.py """ import pickle From 6150e3ab870f69fe9a3db5aabea9613d416e154c Mon Sep 17 00:00:00 2001 From: David Neumann Date: Wed, 16 Apr 2025 12:50:48 +0200 Subject: [PATCH 04/19] Put the Logo in a Design Directory Moved the logo and its source file from the docs to a separate top-level design directory. The source file was cleaned up: - Converted the title of the logo to a path, because the font is from Google Fonts and not available in the SVG. It would be possible to embed the font, but this would increase the size of the SVG significantly and would require us to include the license. - The logo was previously only available with the title. For this reason a second page was added to the SVG, which contains the logo without the title. - Named and cleaned up all objects and groups in the SVG. A PNG version of the logo without the title was also added and the favicon used in the documentation was updated to use the SVG version of the logo without the title. Previously, it was still the old "S" logo, which was from before CoRelAy was renamed from Sprincl. All references to the old logo were updated to point to the new location. The URL used in the read me was made absolute, because the read me is also used for the PyPI package and PyPI would not be able to resolve the relative URL to the logo on GitHub. Finally, the logo was added to the documentation, which previously contained a copy of the logo in the docs/images directory, but did not include it. The version contained in the docs/images directory was removed and the index page now directly references the logo in the design directory. --- CHANGELOG.md | 12 ++ README.md | 2 +- design/corelay-logo-with-title.png | Bin 0 -> 80919 bytes design/corelay-logo.png | Bin 0 -> 21812 bytes design/corelay-logo.svg | 289 +++++++++++++++++++++++++++++ docs/images/corelay-logo.png | Bin 80897 -> 0 bytes docs/images/corelay-logo.svg | 212 --------------------- docs/source/_static/favicon.svg | 173 +---------------- docs/source/index.rst | 3 + 9 files changed, 308 insertions(+), 383 deletions(-) create mode 100644 design/corelay-logo-with-title.png create mode 100644 design/corelay-logo.png create mode 100644 design/corelay-logo.svg delete mode 100644 docs/images/corelay-logo.png delete mode 100644 docs/images/corelay-logo.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dafcfc..ae2c0f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ - All references to the `master` branch in the repository were updated to `main`. - Added this changelog, as well as a contributors list, which contains a list of all people that made contributions to the project. - Added a CSpell configuration for spell-checking the contents of the repository, checked all files, and corrected all spelling mistakes. +- Moved the logo and its source file from the docs to a separate top-level `design` directory. + - The source file was cleaned up: + - Converted the title of the logo to a path, because the font is from Google Fonts and not available in the SVG. It would be possible to embed the font, but this would increase the size of the SVG significantly and would require us to include the license. + - The logo was previously only available with the title. For this reason a second page was added to the SVG, which contains the logo without the title. + - Named and cleaned up all objects and groups in the SVG. + - A PNG version of the logo without the title was also added. + - All references to the old logo were updated to point to the new location. The URL used in the read me was made absolute, because the read me is also used for the PyPI package and PyPI would not be able to resolve the relative URL to the logo on GitHub. + +### Documentation Updates in v0.3.0 + +- The logo was added to the documentation, which previously contained a copy of the logo in the `docs/images` directory, but did not include it. The version contained in the `docs/images` directory was removed and the index page now directly references the logo in the `design` directory. +- The favicon used in the documentation was updated to use the SVG version of the logo without the title. Previously, it was still the old "S" logo, which was from before CoRelAy was renamed from Sprincl. ## v0.2.1 diff --git a/README.md b/README.md index efff1b8..42ddbb8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CoRelAy – Composing Relevance Analysis -![CoRelAy Logo](docs/images/corelay-logo.png) +![CoRelAy Logo](https://raw.githubusercontent.com/virelay/corelay/refs/heads/main/design/corelay-logo-with-title.png) [![Documentation Status](https://readthedocs.org/projects/corelay/badge/?version=latest)](https://corelay.readthedocs.io/en/latest/?badge=latest) [![tests](https://github.com/virelay/corelay/actions/workflows/tests.yml/badge.svg)](https://github.com/virelay/corelay/actions/workflows/tests.yml) diff --git a/design/corelay-logo-with-title.png b/design/corelay-logo-with-title.png new file mode 100644 index 0000000000000000000000000000000000000000..e63c400ac8def3a22f72c87eacd4d8a5c3e21fb5 GIT binary patch literal 80919 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 z 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 JYTkYf`hP_ndu#vz literal 0 HcmV?d00001 diff --git a/design/corelay-logo.png b/design/corelay-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c291d044dfb8aa469088afb6f38319373fab0299 GIT binary patch literal 21812 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 + + +image/svg+xml diff --git a/docs/images/corelay-logo.png b/docs/images/corelay-logo.png deleted file mode 100644 index 605dbf02a01d5dd149e9454494c44e7b6124e7b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80897 zcmaI7c{r5&|35xbX{8CtUgMN-EG7F^85AK|!&q`kwvlD*Td7ReoGdZ6lAS>q%ovn4 zvM*yBQ^r2b*au_z-kta1e6HUgzx%qnTyfve`?)@z%jl<$O8moZT$U@DPD|~7x*Q& zr-qrQq3csmZ(H{#Aa8GP2?w~7hn?;7Clapi_9&_{4+wM#bPsyl$R}li=$n#d3KQO= z`R3=NJnzWfJ#kVeGddasL2EQN|8ubjdy+J|44&CW&^Jl&5Tt8 zdNJz*+e&(GZ@RgE4$yAfDivw^4t8^wPUeLO@;T1myjvk3VC?tPxh52j7x9jnR#R^n zZ|5tuM^-ZbOUexKYVo2)@cz)Mpq1{Fp&?qYLqtzUA)UvKAOy*u_mA1Xes%v6aNDq_-1}&{Ucw*mDwQOA~kg~CQ(G@_2Pkk z{4P1Sv<5DfH^;Jjyw|^%=jS&${XS=PyhEgAGUJmLJ}mu0Sg|%6c3rY@An%H0&`IXl%&n!q zw#%QFPZ%Py*Q9>>93yV?{$(NVABR#LW%`%K+MS{37gdk#$R*#)sairZ!8bJ4*<)8r z6&^!7Mibbq;{~$ku@YHkL*gZU%i0fqSY0q7M8zfJSZ@C3g_oSQNC8Xb5qgo;(=pR= z>6{9?cf)If){he}6c@abGOnvgqx7!+Npn7KAh+GJvmrWD<Di5ZVZ?vCo|X%28~VvKA&cebd`oPR$F%b~Esqm0@DkxEn(+1bB&9XyRchsm@H z#?S49b(+lC3-!)j=hBaCb;v4tVE^J%j~AF1CMB+sUkbkrMG!ye&Y=STfuY||+vuLh zWG0B|sqDXMol^kMtqt?(^BD4-q+!c}J50^(?=;Bh#0aLb~MY$(JaqhWX{3+r8a=~hp3JE^xPYO z?o9vi+#BHN8OVIB1PGD0BQ@~zWyBMjK{#La5)eRJs{)gjsA`e_07b|aXS|@2W3}^n z?1#N(W0z+1+t;_#zNnirC?>3_QCc4y$9)#ImMF&ytbiC+nMX(pUlsiiM+5-oZz#BL zuZnZ2h)vDYlRI{*Hkfc5K+v5s9PqF~xBtcfLj)}GA=C;>J>OaKL4~Ie%d^4fwu0E_ z9%V>hJTQg1%zVn(z_lEdq@O0In{Bwyw{HZ)(PEEwLSo(1YHUypSE<@XLf!Io%L2_O zf-p{7-j*dOv-&Hr+Dof-br#Q&eE3Iv*E&zjdIu{UXa}q~T6Bu0x;`PC^;A_2lGeL; zad!@b$Z)cpBi3ur&YGD1FSx2Texi+bEf_oW&P%F^3SYEZ4_svJMG5huKRu62O~k!M zB6-Wcv`f zb?o5vyuKNw_KOStTyu&^2zoQvJY2pvF5@obOupFduDwb=a4A5;75MW3|v zj7Dc2SVVUr7+=^u5X5rlE}1{uv1r$lE3CLp8TEA_K4VSAyc)hI-9MvJw>k@>ApV1A zW?S4#SZzhA?e5xckiDPZhPP0!ZR$}s6Vng*W#@Y5^K@bpe8*hwZR0jL6x@H=OS6*X zz#yuOXqZXK43j?j2o{OPpx+LNd$VPIqd~aZkFm5FG`g3slGDu`;cFG z%QEIVh&>{3W+PM4hM~{wa+En$lt45>I+~vF} ziYDPjas02Mz-4T^GV1p1UX2bZ(@hPPm$x%~dHmr0Hpc82v3eyr>rJw>FV!V>qVYVl z68@S>zfsT!$LC+0zR<5}L|bkSpZT`qOLsuajqT4zz$t+Tehq@WTGX6R<&{)HCndYW zHI*y(jMp1Xp8zmbWZ_FBgR_(61pHNZuwV1(c*AXhfg&C7{=>{O|8gIaoNu6SBircNhpij)PRrZV&AGWEN2Fhu?spDmaVLlAges zJzq|($F>02Va}+QP^I1IdiorQ<-nNZ)iWZ!EQ&P;RZR$wvL>cWg5>^PQscfQ;?gtk zTih`{3wU_>bf#7o&0wF>d}Vr&R*`dE{vW6HxJe?my#4p}ppuwQufQ*hpIs<9vJ160 zXXHI>?>~LcUx=k85DEY>;=5taMvCT!+F;+Ut_O4e9LK}{3=AQX@N^GKX&`^Y>wcNL{A8URiaT&{qe@8 z`^j1O{%x7CFBH~2R?%gk$|C$CNk80U2zl4+#T-OH$cv?m!xEMNqWxmakqopCm$xgI zz85RRwR@uHYk$$skZFuO|NieVoc-BLe`X77i~lbRoO##M>NUHVQ#<#ndhV>L*ZFd> zUAKe?%7|7#^Uv$a{7O=UsZ8A`DIK8s-dQ`>MUFD9*8>%9eX(pQJEP-o{*?%1qwvj( ze>1crao46y!NV9D^i8|Mj%SEFzn7j%$2Q%@RCvhhFb4qo@J6)r>OC(Vur&NEmm|ho zrk->_qn_uAvuU3oymoR}BzZp=s+0uav=-AivBK2s(!kjLm8 zckD{8NfrW)Aou39t2)kc_8@Nb^`9~`B=Huci45iIHRr?Ew8 zU0Wbkc2^ZRgO^(L=l~9HCB9lrh!x7IgjatCiv+2SEEsRyPFcRbtzxd)iuotbe)9es zXAughcw!~ok?N9}cHrIh<_|hObWfN4_Rs=#uR0}2C(;aSkk?EoBDKRT%O1k0t3DA7 zWW0hEv;e>0QnPiPFg^^0Qt4LF4a1S`GhcRn6eidNt5#oz5{jIc6vhGjo;wkdQkuL8sdtXFZwhiXx*;HEsr-)y{)HEg)>+-w1-;Lm&GqtMCTtAG-@7Rqx;(Fq^48-e4#D5+G$#1d64MbGALkfsp$f+wVtw zlByfbQ8uH}KgBV_??KG+Ch(pAR~;d$=P(h-N*6z}x}kC4pAeO7s>~G@H_b zwU1^tQ6yHKkK4}qCVI<;<|>s?nNm9v1vW$S)VP0iALz8aY`VeAb!@FUtTVe>OlZPD zY+ysOkZ*mBLTY5(A^*4W;pxwhtojG042KXy`du5@X?!j^R3 zLof!Zg(=`puh-iL(8Iu#Y8S*czdu4L!mUaF38aQNuKkG^fhzJ&%wWx7Mo~2{v&B`q z02wQ#PfaT-+6%z}ZWJj&A5ATG*t%o^VAuNa=U*&b?L(Ox6p3l)X`-@%48p`!{Xm57 zA{lOse|z?6ftwan`p`y^qH${;qyvAmh|sm0ckw`JO3GYTL5-iaU(``kcOpCcF~%Ka z2q}HV0_(4v`BlyyWZ_$cLCYcd=)F3a4u58u- zRYVUVKBG}ZJ9d9^2sX2C*YWXymy01XoEZN;w%WOO0j+6l8ja-RfyF>2K{g^Jb-s)M z5=#&akBBk6gdXSXy_lk_Mp=nse{Z;7HvR5^#F^IQlcTWO^_4u&t{)8d#`p9(6R-Mg z6n3h?^vbKed7fRXghz7E9i~9mKvfHr2Z)$W=Z(*z>D1-Y0~(o?SHF2hJu|}P&;m@M z+i$7NN<|Iwc3j!{A4x#bUdX#+7lbny6o@x8efeTXMJUT!=_%uNI?WFlTK(@@>qtH^ z%3nLz9pTZqs-+U6sLdv`oiuLe21ldps*Hjs)paGD1g{T5!fAGBl)CsEK#t1BwDJ#pl4sB*dcC zCUSQHqXrTuy1L~TePKm2*fsy0nlf%EOpCZeg=_AW+F{azVZvO8wD<}noV~XO+jElN zm(ik-Vd4S!AD8qR_oO;UPQn(wFTFCN4ttuIz67*dhj1wDeEwSD$7jpbpqnJ?6=XC8jbIT2Hj`#_W{f1QgS2ANKx>`^+DJ+cY+Pni8OlOtvS>SPZg30 zJ5a98Q4`}KYLtbD>hjB9Vypfq32_?f|aR8|u0&gD@&kY)`BL7^Z1$`jQE~YhfUhzL$~8 zwPTUvae%}&H=9+R2|kW??r=D@7&1|1(3O=6G#v$E_Xi&RZe{bWO~V_;piud1l-G(^B(0yi8x;1gIS0N(T=A3HRuL^jA60-rigvg~ z10`_vi$~A^4~19l+8SrvF@sZy0Sx=E?(7Ac>#xEmwU;d z!s{ZDZ;@NibyDvd5wW-D>6R@x35|Ph1^=Ih=E*!fBE>4;8=9~Wi?Ti7>RP{<7sd&i z87?}W3Q>9XfPOKEj=uSc;q^ba1-hx`={75WQQsgzm(KesUclog56kt)>e2=0O54>p zrkbAx^GI%@1L0(jA#j1#E{(@@TcCqbR734a*WHh589q~oR#FM3EkvbssoD~@T<<;k z>(PONXM2Va7@QB8S3l}*F)Rz)&4&IHAYH5*ofd!i3Ukn&rJEn(1T~9AB%o zNVyRFX0UVk&)snmnlm<^0heBJ62v&I6aft@Ps4$Vr8JvGohupd@WoL`@x^gTWu##y z96*IV02L^#uZr7LP@VJI54Hu?D=D0Pqs*jjkO&Z9sC~5k>mmk7|+VMig<+`U39%U|MDvIuJ6p< z^oTKKN+WD0xUl(f(pGrH4vayyZ>@{;W-8+0OD|FfZBt558zP*m&pkYOId8d$_w$#* z+BIpT!JUz9LR{Y!w$XJgTb2Pv?;x4m%!ad#!pgVKS_rKqWwE$zb7jxhGF#6%S=Rzh zO}h%}wpqaVleCUmie@1p|lL9nE33`M)zD&0rVHk=KkhCDC<(c~!dq7Ljk+{(Wqviu25tHTwENn+GZ|>#@bH zlT;U`Vpqu9GFEj!NnuC`tgNx+-W7;{Wcrd1ERuz+*|E=E!xui1OB7Ny(b!oItls|F z42zoMQ9tMiN{(}&=nDIc@uj7=!!^ciwLHS*l1a!a^dJA z#Kestg5jo&$}8cns(!5g~g6TbG5&qL6!zw>nM-~jYMTV~SXXGURF zQ&DQ1vB7^jDr3KJO}$B;1_Tw%p0}#Dmma}xALp|C(?90nWGHrYQJM=(fQIW&S_UqZ ztS^_6iyg;I$(RFb=fKC_{4YRMuJ2~=)=`6PgvI?DpVG)tI^9Jgk)GvCzvyffT?e_!t5`f%R&&3>WU?y5}O#{-HP^(90+??x>IqLQz-xOtQ~FQ(ED zxM1Kr7eso;3RCTk&Zi)Ri=uAy+8M*K^I(Fk2NoS(C97qLnt58xkhI4BEXYhgC|E%S zh6V~btNMX`CxKZPFE}jF^|9}=&}Fu?=Qw&oak~sfvD#H40B8jmO5>OQH;VXi0pYFD zhzGk_hgS({jJ+3nn*XerI3ffYfmM#}jKD}8GAsmRFu1yUA|=7HulXf5Goe$M#0&0Y zY^tphMIh@kA9dNLqhK#njPGbCyk$;O-*efF`Bv(Zo1% z^O@e3P*0>-u=|X!b^d=bSMnxG*_nXUG}gdg((3tD7(Kt8!(a^ns;(6T%OJ6sk&P^# zOxe4twc5b$AHCTaLwY_v{vWJ@pim}T+;{OX{3R`CE6la5Rt2uXo+A7WX6MXe-WSCh z+Jce&0VI7(4{BZZ-Cch#sRN!%vLkN_Rs8N2X(LjBF5&WS`%b5Y%b&Tkyj~m`6ki?h z#afoZjmR1cw57rSO^2;6AY{EYCh60BwC%annIQA}gXR@98y4$@!8tXITSwd##`xE* z>AC1o%+)q3es;R*cvfy~#NhZRhArbEkDq3<3L|UE)Sc&3CU9&Gru0tvD&p7e z(JJ%}gO1Y&dNEVd{JV0*3pkzhcKNX94o&HHgt#bugNaxi>?B!y0ZJI= zo9|!umke{4wT|ZF8prIt2;TToU?t0bKnkRO9f z%c;skVT_RqY-G$f->skN$FDMN0;voY;dPru@{+-rCvs}#j=#61aFQ`b~1q(}< z_F)+fYmAbWnZ9)w3_*K+Ge^pvTbOnMIUDzU8gAan>syqg`7&=UfjB@jyrB z2Wl$L>U#HbM4l7BA@9J>affBTk(SkjBntKJRs{)0582-a9^))O$ULC%o8_HeQR0cJ z5)id&zzMUwf(k}~Vf0J-EPIEj)%KWEC%(4%`5|jU(&Z(v_&(R-xSD*<32hPgPIXo+ zqWEXV=rs!U9@svumYm`}c<~TWZ6sydVh(lzCg1%1$SHtyrNz{PT~4DB8|vz}baVt? znM=(mfpw8JpymLQ{aA)KT=p(=)qThipW?b-d=CgxZW6 z;wo1%BeG;hTf?gH^y9b<45c&nv4x|annCw}R+VGcemJSNE>G(>pV$UI7{BEucU z1%yU@wJJkNPQgU*z$z$IT-d zYQhi!vq~Lv@jP6ljRK5@EwZ>+O(6xcIQx4*c0&$pLXcGFaRE%bf>p1}Bc{Cm7SsA&RY^=C z1HL1avV-?tj|S7nlMjarAPy(@j7-x39if?<`Z0T@y4||P<YTK9FVb)wM!T(&3-XLinXrRG z7Icx9`Sj>iZV+N`pE8)3VFe7a8$yI~aaWAzzSmXl7IyU?4vauHg=hY7CJb#d*no!I zXSrb4cgNxDi!xb65kbZ3a^sWFn~r7N`o4QI#UnEFc?T*3ooT!E&8x$4!xzykA$*Qt zL0l3)yA<_oasQFbOxn&%m_6x%huOj1A*DCp0X{szHG(G)N=)bVD{HLc1&;GUVbN7Z z;X6NO76aA^j)!d|ND8wpfKhSfNqi{#c> z!B0A9JwY~S)>WM4uE!>NR*0!Qm)*qPP+Y3$ZA$hsjZIsBxi}LPbcy{?LDwEwH~b!j zfaj{gi+3z_)a^5Av$Bd)ub7+&9h0qHTd(%1AMYV8$hwAED%4?;r zJ?jS4A&%Tq*uL<)BWqLG^~BM2*oNXWeyVJ*@#ENb1!ckxk;4oD+=dzrXk>$ivq%OM zA&>>Q-v^ODnVpLWu;=ygV4__1)qR(?Q94QTGK|5IXw#iz+98E;bOw=2Jg0TQRON8GLi>}-M~@}a6d|iU2Sxj z6L7M**-(en!3rzy7nd7Hr<2(wwPV{_RStQMG_(HZr7)8YXIK%ho7{t)^n)4d%{57m zthZS(WCRacbt8BTp-X|L_+cYmJiz6T+m-$4a9=#n-iTpci+@W5rf#0*czEItD!IDG zhe4q}Z1&6&JZ$~@_cJW;Pl`Ul{jJkb(WJTmRun+wBEgv0z^Bly7iHH^V9(wsR?8X& z%|#R|QK&EU{;$_v#I%b*(n=p2x0@z|DeEm!5HD^=5?jqbROG-+v1Q3wzvWA|9K77hh+dyYr!iVQ^f#|MU!c$d zFTYal%F4R)(t$bGiT@|>!xN>CG*Z-3EDrCDtOM>16TGp@40fvn`W*2On46v^dY90Z zz<}flT4bWws+?@Bw5{-;?+g^Sx3rq5S$cMh5B#&>5xkhEN3;x|rZ|lsrqhsFMd!ry zF_OcY^?oRP{YZdD*twb0`x6>Uoz)ol;kE$?_UfM&S2JAD5}mzPKkOJ#=&Y-d@A)fN z_>e~_xFYMQF3GJi8(FpgwU~b4zQu?Pobx!*{6__%vgY<{Cd24pzvYcGbb&ATp|TjB ze3*jiWc{IiM9=L?AbL&w2)Mn0EDWMLymFxC?BR{4fg9V6{Q_DDxxDzeI={{DF*{4S zIsd&rT9IvZ$|nHVk}i>7pfrx0$Cj;Nm9GD&$U#=7F4sy=JF*_SUi}gN*NZ&DvQ#cG zY6MTRxK>zq8$+Fz3dasosH=hA`g${Jc?y3VzMN(5nsX(6Nk~5miC|7{KRX||Z7a*Es~YcmcpwcbybjRLe+F0CtoRh_Ai}w_IlMRTqpMICc+2pRTQhdWQ=?OH zY3EAS8Y@=ptWE8^i^j?D)2A+5G5+Saw>DmNKYY3Q>@{aD1vU7s)m(3eu?0b~o{)a- zv7f&6oBzl)IEF1+VSx!cQvOL6cKoFSfcn3gtQfQOkfD!%)IBUYY~u6zF-ndb<1Nre zEYPj-d+MwUFh+{p8$v`N6Zwf&9-ZqlVTZS8M2*^2rWKY4R* zS<*dR_$?P~7t{ADBvW?dlM@fqK}qDcEzWFg7~RB}(vApq&hl=d=)km1`}q#xDTK5C z)aXBDm_|x}4DSW*`(hbr($&>5Qq=6=q4o;&ib{xSLe%{sfD1e6)SquYP zb0${a!rQp)ohIvHw~Z)b*MFp6I|4R*>bJ009LDZRaee1<1xTM3#W?cv!|kx-#6#*2 z)Qd`B=SbvgUBhd%Y-!!1?(I@*`;rnBv2W4xGg zhnSYip$S6L^QAkw>OdNt5bX8$)}2HnyXd$TzG$*)mcX7y`vyt@4{&mdk$VoBEBbb)ogL#a(N zIUBrUR~jtv$|<1HUXXR(?Mf4@wLj(Ogf@99WgkB+e&{>1^?)pVb6;6%1q5AYk}t3f zYty0$_lQ4?0-u%b zhP;h?wVX$IN1uaRSOwp@Uk4|!Og5PkHYQ!{UomKOa+ImQ3X5o`@hQF^6dX>;i;m>b znvRPugs`@Y2$t4eosw71Q3eBpAjQvZZgogx%Hta|0sBZKpPPR8&9dhUMpH?ATWLsS zf8?BHmcK`%N?uY$pmJ)4#D7`4*5>WTs`| zEr$Z)yr)j-!!8W=YxVhlwymtYGJXSRuUS^j%xb0k*!jHs6U6b$!@^n-v6Ds?OtGVW ztk!PPl1@JdX6pdAWO1}iB8BX}?mtbw&IvB9OJCYc7wNsNX8yTmKPQxrQTJn&tMSFY zMSy|TgLhmz9}*11TN$L74Yue==4w=8CzQa;#IZ=45t!tl86cPA>EG6Y z66%#(fsT!(pf;Pg8)!9;7NW@0y<@enEUeae1FQvw)a2RW$pC{>Tq^J+AxHZQc5^S8I7i;BS$|8hUb5&&G|W=9^o$nrq+_ z*;)Y~-CA-l{2PnilI?F6vmt^w@7EZwhLhRiXH%m@&zZgqok@nE`3EdPHZqKr!LsO~ z5__WE^h3ofko5Cl;T(>fv7Ia*Pi+s%20cTEG14&Q_O^GOWDrZvMGkG^gX$ct>GH7Y{p4G5I597R)NeE6UnlO8d@EHbeiOLi-jQ~8_(x$tHWx4HZRnd#Bk z6Kh8au&;UgL!vRPqD-2Yyr~N-tl3sEOwCk| z`()klK80yt+C&c=6WqQ*+6&;SWD3z3d9zsSZ%%GiR+8?Sb9G#0D$R-sbb$el(sD8H zXHne%-$p}F$frP$J_WYq4t9MV;~+nHb`G>N=54NEoc%0o42OOvSU>u%fiakk;k7!> zD~q4@5XsS(E7(bTtISB!qSh>-GSjYs>Q5Mf-TGfz3sz*Xt=`UBtxMaNLnuIK47wR( z?{5!iJOq-{D$+ED9p0>;KE0vYSSd6ym_kkvop6fnm2(d-o>KPqNJAFO+E3T|HKUu? z>P>*@Q=uc!JbG&eEuM2ta{o-wYScfooCYd<=+fX-t+4wJ$cJS;KS1Yc~}@W>4UXMQ)SW>ZF{{mMSU`wLpo-cG|wGrirg8&HW!mCPZRa z4DO3iHmk?1Bao6aNXZ-|uh2?`$V@6>AqAHQj+%Ct1KfNNqa-J|H-?mVsq*en+e4v9 zYjKrSpC;iq{?;H96lSYC{cvA~zpS z4#gJ?B~4Fw;OL!}>k)D{sq+&TfUiG9J+^_NpYkLY1)KzY2UaqA)mj4P>6QB%?{QU} zpaA{RPA`uUl~oLok}*MIu^I zPPf*}h8_E5Ti+;#pSxrXS3Mli+m)Fy!~W}iXy+f%eqR7qpQ}ucpse{E+`8e&G|%Y- zxm~);sP%G;5LOLTaBH*#R;C?upG9TqV_E(0y30$=YJdK?(-cB$%~e-nqF{ac7>Gi!BK^WaASWm%IA^jo2dY>1p@6TFR7)MFyVfpAgO`g%dWC0LE%uA;1GYJFS}F#> zw*k+8SN-1h;ifU3=S5&+$YwQ}Pd%2^7eNV*yckqcd(4Ns)g?e(b7@pto90(pj$X&o z2LkcU?|`{YZBW}V+lLP+@8dbcZ|7eV{CO2*<6O}Z6d2f7pjNHA@?N4qpd>e!_`5rMG76`i6dB};Lgrm*99xDRcLg0gjc7_!*k*CqnUf&(^)8gvet*gYnWb)g zkKVLVC!Dyy3KjW{r(dRJSqYvyW$VG3dV=>w0&QBNRnmJAK+0<#Ep`nUwWSDW*YSZ{ zFn+%TBf^r=YwItpB^Mofb%m>6r!Fd{H=IHM zM>Ku{k{7%yc^7=f%UJ1hQ)qNzVe*pxJJIQVF>)>^k>B+i zLd1Nx>RrLKoZ}f5P}A06bMw9G=dHhyo1q3>M?;o-SL&Qd*eP#T7{M|aSWXlCoJmfL^PAln#OB;{O`S|o z-KfY}HV1Zr%Yeg}@qdTq#P+8@R5s+iQ#@Qi^&hI9tom9Qc1r4lyJEJa455WoDz6hG zxs>Wmpe7T+%OB&&U9~~?*V%q2{?VrAplsTNFXiu{UzEHcJ#HZvdS^ee2{iqe{3k{ZS>{Z@tb$Ay`7A<`Qr!Zu5ih zJUC~DBiR32C92Y@ZkaAo2e%{N%S__{)tko)3A~5YA^eDArsI`UFPmBPo%j^y6#!GO z_TKkH6aw4j@qBIz{gVs(a`V0*DJ%2rAJifDPI7XGr!ll~Ur(#KvJc8doJrX89T>+B zivt=VEkC~JjZ15Z*dQ)$D~wNKg$VeC{KSrO4f_WI=vPxQ9|QNu?1>s4yNQc5^yF^1+Zrx?&OJ~EGB>4bKXr&=|v6FiZFAYK&P0w zYLSEAYV41sKpqpdD>7%DE4%omeThP(S~8*Tw+#w!iz|(G$dH*Bb!pluejS7_-It~BX@BW&utu7$sH!M8*ZR~ zBxJeg#z}f=)Qqa+!x~}81b%L*N<>#&gj#55K^cMpzBl6iiQ>2boa7w-MRmI?Iw1MM%lbrg6mSQDysS%@eHac}g51_(o_?Xfg%gT=S4bZ8?HTq-wJ zC1yG|4m4I}!wr@>HQ?zU2g*83t-jP?v!(#@w z-~Dy<67|w2M1hV=K?bG*9dGiN@d=BcCB+y7v_0)gC+NU*o}ez(~L zyBiBy9M<(jx`e=wP1+q(z3?>$Pg!3i%T*Xr>UUTO&~)9#&e?iFecB}cTHb=9s3NvFMof_qwUxxdx*7{SjC=1iBlc! zH4-^1mEt_p;vqkbw(VL*d?9{SHU8O2`9x07Yx(kzTyd{(>Jf~piP|W|9}COAU-&_B zxD#t;=ee=f0`2DkeLU>=OQ}+&e4-<(+A-es1p-xnHFvS7v-qUj(}6wgi{tog{Q9+U zB=Xu7)ehA2X<41{kMiv_bLF3)p`(|MXdHb>|5L0)#9mUCzxw;$2yJ3#&}Ff35rkp( zAurX9rr3L%xT%L+0G&U6?npQLElkUk?Dx*cjvu+WX;sVkrFnllp8SUZr8;OjCx}b) zw;hi1U8(OA`z9?YqCU8kuBk~|#$5u`3Hj9A{91{9?s;`B{EK)MnQ7+-?GC^d-gxVH zrC9I;4TKi}z=7MU#JwQk4l%oIK=~~?$jMYq0c}MYkQzn@tusATcMVXlOHBAF>8*LH z_y`yr8>ZSNNV;d+asKC3kcn~2=db1AZ4Ss7W>nBioq zs`pP+`9WJHnHKX*R`6{}C` zf@jtbGvKaBCHEmE;QWcpT_rlqMO^>SUo<=@J8JahC0(K76X8WqP)|s8;?!E|0a;)73_2ULJgFH<+ToEhADH3&gmqGaKjnFh+V=2fVg{ zOL+7CEGUhuN-|z)`zaTkm~>&~v6n(fC95(BWTD3&9LIjfGUFZ7r>b+hpSScl#dpvz zkjUhkizCDWo;D{cXvt*ISCmx$8)K=$U3OR0PHv_dDhulF_PjAKcb)0L6jG%ogy$)Cd$S zcDYKM(;ie3ApQTrx1x=NXa2bumPs?Cv9afW zhLD*&HOc#dlsgq)O!v|Bln7Tdi>d+I<9{r3TTvMuQ0L*WPH^)>H-xTw=n8C z^JzfO@L_!MCCq6@21@04#+;FwKw~LnmQ$~G|Je|EXRV}5)~{Q{l*w_YUx;Nt)5tIU7U zv)0igHtWBF>ppcY$eN5TUY1xfF0TXyf;cWoSLUe>3#j5d^ktl zj7lH-bc^jh_mRR!7rB=?q|Tqt6f%uEA-Tg6a=PQnys!G{aLMZ8zlK()Zva|hH=fVv z1CT$kR8WB>xG1V>^9L?@{?An2iAaGQn=%0HvtAwfq7pnIWr{$9hJy%(>`ba|m-UZv z#CYX@aMtuhvR4j00i`i5{g{^UDubf^OHUj>QpI!2DY!sLTkzDVIpK7R@cCX{Bi!xh z;n(yGZp#_n?3ZEZday-;E{EDj@Yd;92Ymr~DlTmnhi9u5J?m!Y`nqtVhC6ws;t1;F z(9s??(C}YxgU)%EJf`Gn;zf4X1_8dqEbfokOIS*c^O1AQ;k3Lv`=u5(Vv z*Etqu+QD?v{pb6+chQg3ei_z9T)yGr>tjW=(uui%Pi+9j!p@SUhdZ|(!^STk)N?L;>milOpd(g}l};jwJowx19FAqR3f!V_){;Gjt?Y znkn$tPfOq)ZC7RMwJ)QNkiDj_){L<7dbKRy3jM$T*xL#Y9{eL_)ky`Ef7Ysa#pC98 z^553_m&?L5TOyf(kYElN@wW`SrCfEUTP%N@u^sU8=2FTHkQj8?C&z5*itMcifkrpS ze`zB<*e@l1+!pCFURkoYeG=1~7dpRe^hvbnVilx&o#>5Ye)#jwY3TEClbeGGV?Mu3 zwGeNnD5i&^-MQ~iG;gERH)(#2=eU>e?3FW#`YbOMo9>ek>jVMT z;AoJ(HBahhgCCccQD}}{YS57*gBs|cH<%J z<-2r+qLhr3$PkY&S?*u%=pA4C%Yd)=WhO9JlatxsF$z*!p0!`oc@5`meR@pxv}c*4 zO<4g}Vdw`@x$4ueL;yYmq(p0;9}RiU65LTwV=0yhk5r~DNwS`n+$jT@4oC`D3#f47 zr)&R?4KCCH5L5;pFCnGK(^bLBFt_0IQyHbdg@?)eVD4;7?T;LTBnL^R07;__)BS4i zZYvF>GD0)YFXhf>Q;24?mhwJbT*F;#b7r|ZzROfEC_6Kp%I_BR2_mN+JLByF&0BE< z#BqPgXgE@N>(8~9pST9=P{l8&>-XWJ6Zoo%Hl}XgcjLF3ygN@R`Uu^e5;FyJ_IQIR zE@GEl)A8upz;vf8khW_Cqni?HM}Ed~u?&5MDWOse^3`6=R)C_H{~t|P9TsKxw0G$a zDQR4#y98-rLFw+0?(Sv*fu%)C8bMmR8$_fV>6Gs7`X1ij_5I7WJp0T!b7t;;M}`U1kM)FKC~pWp6dAwW{P(Dk*V*nAL7C!cJot#!;R`ww?PV6| zBHA+-3y69{SAv%pU)#FRM)n~9?Us2IcV-%UIa5zAK2)GQAK&XoG}>@ z4XA~2w{2th(IfQt{$eewRKkx)(89$5HQB8y{g-J_#cT|mDwPa0vA6Ce{owDot zfcnGYukFI^Ry7TS%i*bim*1O z1}y@9&c&mM;t`h9^Yl*!rS33;oG-FaP<Wane=EFAPR5Ik_^pex3)Gb%v16O;Ak5IMTlBpTsJ5W|C3r@{1)iF&#ddTgH$Pp|L z5|*2S(^5SS$F(@tT(&b)oZr)z5cJ-Bi6oq(7OPtp#UN#aMU=XADJ|e59gA5Ghk-P} zRZR2Pj}MnKh5C<4AAwyb{T(L`uj3}m(Q0H6IQ_&+snTM9eb?26Nrk%pHaO9B7T?lG z5Qd6nj)$)tT9`A-_C@z^UNlZ>M?39a_8QmLYd;;uF`EvjGNc*X4^XJ|d>r42cu%Jc zIOe!H39$`afu!N6yajI^0st(|6sy328Hn9s+n$=Tcl;G>2|c@X*-d!@4}F%tJ1~-^ zTOEBd038Xy3+uI5X||f=m||cUf~>2Y*UgrX0k*tf*NdCTJ$}Wj$l0fpW-&;PtGm4I zy$z`BQKEBS$ZatN)V5{vj+LT&jUA`rj%o%WnPI~}v3>;&>7Qo!ZX$>cdg~kNFsOjw zh??z@pzE~tIJ)lv-B66-SHv}QVFf%UQEF@GU>}adOR*+qMDJm0$b@jH2wMNfBt=MY zv|W8(88(fzWW)4aElA{<3n~v4?dC}N@LzifhkAQXeABp5k!#}l&3oREibg*VJnw7W z7SUo^4(>;9x$OuEw-mU-uA0D^3xF(&rDLNnH9!Q$UY9>voE#7xabYHYY>{N7Au`fS z32jW*RIsCW5r@$~XMYZpo!My8Xu=}ZE+CaZCCfwd+e#tKh)LC_>28{#{?wBdz1ZXXudPTRU_?zv}d$_KitQf3e(8f!b!+_`<-$Jb!7 zVUWZ)iv}7;VJMt5 zv-R83u39bXPd6G-=HDZA^!ZQ6E2L^wuaf3?hwf>)e8QFY)0EVV$W=@yujYH^vXlkL zv;+?Gh!8c?L$K3AF*&fsFsb_)ytx8~Ri~4Q>D*Rh3z<;qX7t&Mr)gnrN=RH5>?w2` zZx}+~o6S6YZ+vQ`b_u}zyCHFX2E4HR%wp?SR(8z&prf3Zc#vCDXE!>MxmJt|GfX4Y zItU)~Ah{j*N#teg8Kx>JuwIW$yz}piGn1yIGsr~ueBO->F&8vq~6Kvc>z18+A3r;B^vFr40( zMRN7%3Y$}L`Q&9~dQR1D19$^{ zum+AZh{cmDU@nV=`M81M)>}`!u0tDbg)7#DvFjPiRrqwFk`%5KjGo$LxA8=(2%|d+ zWt+Tu+5Cp*P9L*M@3+onWbd|}|1`iqt<$<8zQwYi*1-?Tlg*LXbDenfvk9}KrZRA) z&R&yGM9$X7+jtF;Zg)u9lUp_;&;M1U#ptT%Q-F&I*T!g(cGfU8W(A#T$g1qu)XybNsi95YH zJ7S7DP%rL^PH0<`n~Da#WvpP)yt31$OualVS>3U?(3WinT_B0V==WAZoDG|YF5!3F?#j#;_SF3Mv5CKhf3A$ z>s(6g>oCQ~@P}W0k}dyJ)1PI6&DV7EuR_O5JQN_gXP5lsN`5|^Y=K&T^>9&uBCGp@ zgax|uS+6Q_Ai03ytO9Ok^{UfbnCDWUqdUOz*sm3fPIiw8=yo24kqCqBLpO+_r|}ruQ5SAmJofqCNBvbyA|2; zO>r^x|F$}8=5Ikow7HouZgy6z+5Lp`M$~ZL#0QFJPP9h=F7%pNQ(#f1K=?%3r=$Q> zk9a+El-eswYjjuR=5?v!Rw$y(2~hkd$asViU*WHib#Z8Jz!!t*)-QKosR`1r2ID)+Pn?dDwOgu1J6cmV8p$6{Ef$YVwW%3R z->NAb1J91EAzopVb?y2(nogfYI5>RP{LB)oDUI!L1Q2w#gj6bH&!M>c1A!uBSVsN* zUrTf3pW&g;J}j@kB&AVIedqmia=})!UaD$tIc%bFtn^Xk1}HNXR*iwR>U6(gw2lAx zTl4(}%O?!H-@g;N1rrtO%{F8TF6r@j?Y%gg?-Rz0A_<7(J+6N3u@3ceR|*OG==Zk0 zX1f+6Y!3{;9+~7Z3t#cBJs#2QQvKw|>(GzIw%xnEDEN&8EvrF~^k^$Rj@?L%4L9gU-Ri33qWm+4@h%pcEeo|X)h*5;T99ZhHh=#{b!VZQPYIH1)A%hh3Jsw zAI7YL=E<0*=1fz?@5KV8|JyWd^;BbewjdpekvVj-4@K*q3sV{a5$ig~TqpeaHR^?E zpGoY(;i-i~`upZ!zDKNVKR@^~SW9%5Fzq%7cNR*zvHtCW8pLPY%d7)|M1s-}J>=Cy zfjtvsYv2V(sjhgo?@7NlL)cd{B!NA^e9`~<;U!3SyZ4)cE)hV4=$cA*;i~WPA~c8s zl<>1lBY47cQpD%bc~HeLL3SVt^z4i{%_J)vp2gM|skcHbC4~WfzXNXdU*N}4qa?*V z4fNk@tEx}F&C1_;EHl(NBA5Adg7SmT&}(UbjUY)5Z`)4ng0GA~T@uDj@%LznI`vM? ztAmu=d9i`@zr(ARru^N99o4aI_1n{%OB8CEji4-7QS;d`^7M@(u z7Rf!SGVVm+Anz0aFol`y3)x(I6HUzc8;5b(fcd3$ke@QUn5_-`;f~5 zpA|#J!P@uadwc_n6dFNUa^m$I8Y9964#E$rN~?0l{puy$ccSWvDWI6sW;&VSNT3A~ zC+K7&&I#@~kzcn@egM^ppP(aAzpqfIp=Lf#LH0;nGbDM_z}?e7tByz7+H+(~R9D;N zaT(VMy@^wzc$Gw9)wqqeq9qoq9+#^$M&rf9lzK+S3uX)4Gq{(roO4h&Te!(15Cy&t z0x9p7xl-IC?H3{Gpv|xTJ(dM-m~h3p**c-(j&-d@$fewN419amOx16hgn$^VuoCfa zwB^pZDnmPXI_jbSCzc*SbNm>>hQE#-;gKU{7LY1IT^SC%Pz$ zpFegjz~DI`L4l(cYZ?b(0b7AHM`JdWn` zAso)sY-G1OwH#(1Mbi7COW97RroRoVgFDovS0Z+Eq}6%k@U$BteI58-6%@l(1`)O1 zunN~|F}~&;>ZAj!nq^j8{#u5U`GO z^6a`3C|T_Q`S)zh>91SM`_9bo`?{cW&XnAUy$rSO38))32@14ez(mo?5`);x^dI6a3}_+dk+REaD*oM#`InZL3ByZp?2gSQTzBmWYW zCeF~|G3f@m3fUU8zqx3eO;3=9Lj45~TNUk9R@AyU$EIjMmGO{PaB%I;uC9hSWdHMx z!ACf!Ce!g8^BapRkuw7_?QP)#r-fBZ9i@WeIRDbuO{>L@q;V-ee6gX$^W`toKPsLv z;^gP&W~j1Ta%b1~Dg@v42)(g)T)eyLbjqr)VUV|WUT=6VXKm|eJ?_g27$ZODV@*B? zP`lY&tcZK$GvyM! z`Gu4Dbbnu1<4pWRhSQg*rXAy0@#RVy2eI{NXDx?E2$#2RUapL9;b2Ac+o)yql?_B( z5Da_X`qSwCB+Mj(Hphb5kr*Zf0=Mt}45lcQTUspc&hGwx!-o^QRQ>07jX{zw7(%Dd=TOMXvH1@03`+h*$|jy!|IUQ^D4%c;oo11 z9y_!6cM3GMZoG80neGY@fSDo-969Upc;S|XA|$-}i7d>(U(Qvao`wEYj9=gm6BKaV z9FxzGDw-P~d@WIY>-XHxLe04rWYON(pC(ZZ7+G5NcrQN`SZb+B?K$ZE9APig%0~!# zCGqq=NA!6ll}k?jY-BLsXI*w*+&!KY9FCOpF1akg1A2{tW9Ux~l9qp%Vds1=q|;Hv(hX6viAg}*3uupzCtwj{K0FdXbj2-*zU z+Sp_o1}{W{8n-*zw_MwzoQjdJhJdW)<5Hzdg1tF}4*tE-e$>}H|nmk|C zmo95}*S_kP?@*wEDmwbyfdC2HoShZ?;)yZ3CKTmIt3=#Vyhe9tgUcNb7a~EIC*8Kb z_Xi7H>&s&6fozDbF@Jm4DZ0L(*ZYxzIOH)x?YWX6;(mA9;Py_f9LbRsrr|%ODU4n6 zuQpjib#u$ygY92}AjEo5&9$v?%v?~+k#WX0Svl!aJ2g@Dv6~-65VxfJI{H3w*_yI%;&~2(q3EH)#aFRWQR-`Wi10UU;q1!0K1uhOH&2>dRFrA_rADyaa`6hoMEl{Wcw@0ujx#GNQf@q;2ekby{N9{Q17U3l z6eYi{lqf4-DqmDDB=mZj_357zXh&ekz5o>PqCs(^X`=+Cd*8Vo)0)L!nQNB-6bttJ zX}p_Zfb*AH+!1+vpAJ1FlNquXA>raL!lH9c-oEz=5X>mzzqI%E;00&uDmh<8(sAR| zdoIH3KYS*6)DA4c{8=5{7Ka>mY|bUS|kym11E=D!l%W};1+=qe@9mmGhW-)yw3T<{37*q@zl@@9p-9V z<3j>5A&6L0h@4wKpiH}$eLH7X=d%2=_Nb+I-C`t~aPd((mWq<4w;S(<7q;j>?amRH zURp6#iX-3Jb)*A_%kgvQ|1+v)ob!_e0P>Kz5|a>d<^!fCqQmP4vV(rGP-)N+ZPh#_ zAj;UctB3YHMMt}DM#V%exmdUk_6y2lI#@mqwZ#;BQ^ML2YL5`sDeV})&;#`_tHGRw zm4Kq*8%PcoU(WQeI@p38zZ7M!X&0%wvynWs8|TFzxN&NRg+r}BMEK-R}{M(;dYjx>U`R-^5 zgYU~WIB1!)HcgQOxIKr21@GDNi<-I(EA z40}r@*_LTZ+!7V-4xQ90Wv)={TZ-ia{gBVi03^0f@$r6Y$rCxiE&d0oN&e)R?eF-w zth|=D*U!{{pk}*(_RpKkFuWx&7q5l~ZYC5inqL5Qe$#i?e;+`PRy@CJhu=1d{GDGz zuJ#6&Ll+TvtUg5qz2w+c-S&(@{f9qn=DGT}FJF+K&3+mGT_-`X$Cq4LBIT`5e(}=-7zbZ26>k|1>O4_1?8msPm+SC>g^xL{fLbxM^;JOPL>#aiTVxuj6{9~U z#eYM~PF(#)f2c3Guir(=tMih)R{DD^DK=C$}(k@i9bc z++W;uiv>$5A_^~v>%Rhe7E||7`gH8=m$Cmu+=8@8sB7J)++$ClHET+Yef4(8nV)as zLuzNihB9^kJ_9x%Pvl;rWbJ+DaRnINO#i%i>KzhR490b1G=sCoA9YN(TR>JZXD%ov zP&QdzLGD6t1-iVcX8gflYm8SSI;uv0&VrH(mdYkO$S zr%DnFLi5DC;?-;XR|vE!aH{~L)nByH^L-WFz(tS?oz5-C&FT06i>mm!z}6~jC&+Go zbXr2(&jMV*t#J`4w~IO;fZOrOk3|wmr9r^-;jx>ou3a7j3S~UDXQ2u#?kg3jp5K4s zqRcPg^@?@Pny^0lgPVz;J-VeLLf8pk^l@okenHyo$x+a6(PESqdO-bxv|8xfVx5KL zM8>t->u7lY`oGbI5Bp&E(E>66kKupRIy42nqvM7iAOG6!#g#XIW79YHT{rKaIaYkk zq~9>ZGxNC~+&|mX)W({E0 z5NmAJMF_+cadRltZIu2a`)MNKy{eT$&D9N!zhf!3IZERy))fiZ?R>)HA(xif~T=!aKodtmnuIb`>HSCte9lU zG+w|nSTmUpb!=;-fzVA>pp1N#iO{Kt-LtQ{&C*5c_F_LF0XqXU6W0if&4;C-?+*qT9x#5ec0u#U3{liN=?m ze@+_=RmWb@1Ia^@&>>C#lOit=*DSp}H?<+dx}f{ps|ruL7@iBL(Bq*5qv~*Lt&npfvDZTq+5+IXC|JBmoTzIbbfZ7 zn%#v$mrx9QgyTkB+H+Tyy;_nk_cnl&gEGDveG$LMJleH>tJd0cYN4OQR05*dk?^H} zT>YtbI%OTJ#I&C^PQFn>ys?}l&%MMBkI9H`m>^=pMQ{{mTdgYBt-U@Aav|oq;s<0e zT$92#k-zZtZ1^`zP9L<-uq-AA+6-rXLCu5tz4{Ve-94#8KvHK1_+R&-E+^34Zq-MZ z27csPTpLTmX#1}Vb>$c8Bh!=m-_0?BpIupV5B=wXz&{^ccvW4sCYm@%_E8q9Rx;j_ z(^jTc0yX^@fR$TF}7H6I5b(F9?o(2Z|Ucm z9J;bzxgsx}bLqk&tKEB^jvj^P!?F;8)WT0}{yicyP)((?^~#~arl2qC*4uMvrW};v zz@MM;E5nc|9n2%!etUrBnX4jFRj2zh0`CLc>53s34yr2Cza%(M9*yHMvrGbsr%fp9;(yL{NGC z=t6PCo_%x-xucJ%cu~)6!e(e!aF2Eq_mm9AK+Z zt`F;e$>;!ZPJ7vyKbi99zORa7KsioX{Wc%ENs?vzpRFo@S{LN@I66&=6C=^G6tZ34 zCrz2W@0}8$R|Q4t_@bcH|S-+g+>Ic#L&Lh0}y2|_;5?=03lJ*coqfkKO_yJz|%Of<0c z@Hy&4D2;)GiY7*luyS&xM?Dg{xvU}J9#Y?!AdON=v5pl^C<23WV0A`PNqvHE-epx7 zk!NTCO_`JdH=&oHYyp?UKfeTUZ%@YWfjS5;#fqO!w!8Mfqxqru?u&Z4AaD`#0O*G< zqV5Jt+aG>AT*r6PFgSz6!65mx>Z^J$yxL+3*SQC(bv%48x~b}I7NR{x*rxZ(J0hfD zpTs%Q^`y!K1)%pPs=}klat>bqM1@kXS*g)Vk=mcu0cYsiCJv57Jz4U}z%Eln6{5;? zYc6Iv=D*|?^LLbWT^JILiQ#_xDvm6*ZaGR__r} zE%hZwe4zr6H6kci1-9VEbV>tZ=aW02m5{%$?%q5D4l+4IJI=JI)aEDI^kjk&p=F6k~yI zl}eH}PV20lbhQc-_r{3S%ySE?FB3p3j4Ze~?Et#>*OnfnFkKZodG={%v?D2|NBslH zepUjS5C~9aEl3@-e}bW*6!_IoCPHEG-q%1PWQJ{l?Y5MEAogAZ86lYnx*sdt|E9RU z<(vLex$8KR?1nS4yEox{U*JW(I?xT@QKrII4rZ9#{;V_LJfEu9UgX0fbXTCn65!iH?SKq|4_V6+`$NF?3kTXEvfnT=54s$W9^hSF72OT4yanO6H+9xJ+`hcq0;V84!cNP-IW^_=m8?L%Wl5#yGAXhOxveUPx&Y`YnzDX1p8VDLgri)I-FnF~ z`7npEQ{OQWXew2HEbSLjGYNZ=u)X+t<+UczSL{8nt43Sk@R3NC3KwO z_a-0|)uXK1d=81$ATX8#GE<}Zw1YR$O&5@!^g|JI!Tm@0<=s;~reYI$hoTT%1*G~bFWzA|a+b;#_&u|X$FZCcj7F3Wg9&YnC~wAq{^)#Q zO?PD}7ad&v8no%A9jyK5E&T6b|DKSx*WWLQp!vJX);QX38ZhL`asPB3M15a5jCh<6 zsMc$}v@4(YUq+&I;0hbX@$2fh0Jf;8`^Z(S+(TV}hyxjLrhE_L26m*a-}HG$RDs+> zi=77CU-63d!+fxPQn_xf?t%P*)%3PT@cJho=b^p7rSvPNf{+|JBlJJthVjPYdRi+h zZVx>G9^%D7H9L}N9jfvj(Y4k4B0J1C@gnYcw50(x+oZKWF-JR!F8lD}P(QJLOSKRP zgBOYr8aEUz^}lWQpHK#pB)tLE69Lkq5PZHUBGNuQ%5OZFB}d}^nX^sll>}*!#x$8Y zJ+{36uZC+Ia~hRGc4_YI&oKZ{3Q&M6rH|~ zKCPA6+wo)+Q3#AD-WWOlH>?0~1u*?pr!DZ@v~5Jol!(BZqB&3#x8M(DIi@pvz7QLA zoU|ut_^Da+@OhO2vV6gwa;?Zmvjyg8m3OxP)vf9PXfMf!UqOmdCx+Y&tMwyl?Ai)m zb~NU@K+l(9^G8$TmE~!}kmBKIMzE=THW8G`N%p46wXf(*k>fvs!s`ZL9Gb#}2YUSL zzPC5v<&-P!`u92FD9`Z+=#Sk6e*TNw zVIKbu!F73gL8okV{ZAtSzSr*{a=ykkG5KYN(Kjr(LrtU|l$-H9U(vL-mR?Y{clr`5 z4~5#1)ddtZUkNZ|a=o0GLKhg&d`1la35c>QfWd`gt~t*yP4Q6{p1p1hCmf3ihC2y@ zw=6?k&&D!pnpNwUT>arJja|)2x^Qw6iNM@}1>p<|Lqo<>12F7t@#yy1-Bl;8H(kIGbB0iuB6}dZ`EV zK0cL_*MDngOaR2@8+27iV8_p6c%9ss5RY~%o-PwvnGdbG2-SjUj7{dr$&1E3031~* z>o*rH+FqWEi-6_w1ZEb_#J!@VYo{G(H}6;{yDt8uIHJe}j2ikHh6^I8N9)R)h1xh3 zQ{wYt(5~j3f#i57&O(}|JiNR21IUJ7$>;(ov|g*sM+6otw~5BFMGXk(o`ny8rJpgj zPSeQxZS-RKDKW+OBoV~ykk!Ka>g?r4tFG$|AlmNQj`qJ-IOOxOM>4dP6;Yn)CWx3OwuEo5$8E7#+W%)qald&pqKKk;FUW(vYIoFH z)5-L}4{b+;a-`tE;wuJ!`&Q@PPF>&r&gf zP}9p8?Rdv(_B$un1(Ih|4w6{iwcRh5O4K&gCGfSawcFR-f$7ia_}_>Gpc*c+oZETi z%4XRp^J`P1zQUJ;?YtB&t~UlZA^!!r@4IOSgjxAaKWO#R^#`KUmv}1F?N@9*Ph9>+ zH}~tYr3DTNb)ex?G)M<Hq_HzFbv?otz*P@|bW`9!v7!<3yrS$V!KXe?FJmA8g^0 zne7XpN|VU|41m`E;M%rb3{yzuU$=?Q8IymOXNB!}LcFcMDgqzEE$TSWkUnya8Z@3j z!#8XBXgTfyZX7cFEWU2hDsv!8Hb~R|@j;gEQ5EU{`cinY;ysZQ@HzjIvt9&W*;$Xo z+#}3!)>f0c9UaJqM3`7TzAryEBrjIkuO|aLpt4zpz2?!BWPsc;_(rNbDx}rijkD@4 zkclCM#)uqE!bM_KDVhTNi1DQUx;6Ie#0*I(gG$-0x|=Z^DZn8wQtdJooc+3+EnA7I zA*{1M{ymVDGPcAhSn=&6O!;BC z@zI$yulr56Bd%(Nlx|1`{?>!N#T)dSVDWn~5whNuJ`4uy2HtD8 z=#2BTA-&A6AtB`It~9WQzzvsDX!cA;(=+Ka)=KQ5x?*0LmzQvr*IZA62s)o(MJ27GJNEoZLn5bwk-%8 zqC>dbGpGsFb;}R$kWt~Lc@2=7v@ES$eG`+p?NP{Zg@D?6znaR6aJaP*I76lxZQ(bB6z>S+OVGTd%QK|l;i{oQ+NSB^Y-WA4 zNjm(Id3N?hTglgsOF}Ia8=Hi@89ycUWsy;VEX4YYbwGo26@jiClKFe5qGs}O*i@6N zgR5+ql@9{e&z=c@v4-gn=?37!`ohuCoIIz-KpC}ANO?r!C~3E_XUv9yEa=oB&2lVLliD@K(q!x_{FZ)so@CZs_{_0T^e zR@Q!ya^!`lk)@2D589b~i0y|SbeMN^_6^yR@8eSpm33o%Yz~xT-H`X#sv0XJ`g2Z; z#b(|hK`|Rt?Qk1h-_218m(E!0-q~Qx67Gdgl|pFn#J{F^9W7JCkkO4kX8I(p-};L@ zsQp@R%7j)Awp`r_)$)Nn=V@-gmF)>&z3ruiuutm@b7W{oRuUaIP+w=PvE zYuXbINj>0a9A(xdTyV)(2f{`?lr`+2jZE#}?B(p62Oa?vG_@grDtNEUw7OhF4^tKc zrG43@%}NLxs?w2&aD#XtqwSUvJ*rd*ZU5;P0`iYfJ50}XoJBJ0_8`>);zQlqT zdg-QsK9BS!r{k#b3$>1WVgFjv4HP;hvVE{sV%%iZU6o_~B;Dnkqj7NFr*y?qEn zh*9}kXetPdOc_JhE6& zc|XrZdt?D>vZ)ulL$DKMcVu%GPm_clr{WZAr`gZZN?O!eX}JcD^-id@;9_^dFoIe) zEAl!_38yxDsRrbpzQZqL%}aSFPC_zCaE6Cpa6sLkVbpyaeL)^z_e~LMT2@E!2jtzL zeBbk`J6dWMYA3}X*MQ#9BXuGf0Po!Z{sN8Gp~{#ehb@-LTVD!S6Ge=Ge>5w53ezM< zGo)3FF=R0u;A~I(yd?~9%V1_Qp^hT-aLEAP*xjBv)u*n*zTOZ7`&XTseuWXE|g_qOw>jbrwPs#`7nu)J8 zZZI0_n>t2n`{^OKbbpoNtO8e5C+;==h3EWR%WfEF`UE?fR##xD?x*iZ3Qoe6fqPCP z%z02Njh$zTApOH@(A!10hSN`@OD6Ce z+qkisGA@oT>;P%WXA1MWZfcFe?&EGHsqi-4(0uf?Z?EF{*L6(^D3tBt>__DR=v_~h zgj!bRAU&jN^OXYytc-XTLr6!BbHjreSpKmcb&po0-jS$(vG>2uLdnet@DHS5Yqpr@ ztia2QBP19e6_oQ#M^0-f_;xU)G2}-5q-fWX%qNA$X3lz;(+2}%p3eJIIbS2ezC8`* z^2yivAEE`T#Qp4>B{5nYX1{lI^L^OTeiF>Xs%)_Jq$vBK|GCQ@s*&pZ>NRNa6X6Sn z;R&++0`$G&Mblf(in(4K-@EjJ|GYwMzI3$ALp$|oc82QhO2BrC(b*V`Gy>jlce%Lp{=+^ z+JfJP{w6nylTRpm`=)LX<(xXkqJE)&RivROhrZvOf^H*eQCM-E8+ zhF*y5Q7Q8-mww?2VE`30&xb>n&8YAnbmP0+$;NlpSRkkom~Pz8Zda9+b`G<5quQIe z!+DX!Ck)zayv20s|H8h%hOlGh*WG7%X4zeiyPKVN_=zV)-~_2 zGy6*qV&_CA^Wb`f)F(1~-5-E6eaW z!DFyN@*H-OG_G1(Ys@;SMmb`H+UbRk@{-xrQR z+m>1* zmfpBkMB)vGb9-Rf7k0XEfBIDQoo)ob_}Cc}l=BU2X^zA9#U{ohMs11Xz3GoWqh`C` z_YAX?G2(~{lTB)W5AwT%?W^`i8sugr6=|l=hPfd*lebLXTljbsc3R5h1GrCFMk4re zMOq4X_@JgHMdFaKldr==dEq*Z?*~w6)l7qw@4*K2M`XFS5C{l{QrG+iQN<`NYmwNl znZ`Fv`Cj9r?+@j-${Kj=4V176BQ#_wM)p$ze6t@4m8@ML-+_irhbzsPn>=9ky|bOq zxs~ok=ZS?+Hgm?$Yy}y(EeGu~&w;Uj?Or=Ne6TduJo@>N8jER|svC36*sbuRUp`ia z+1%P?j*Xwh-FtgL{mMWZ4n6i8@C6&ZYZxrA-KT+6oz{c9T#&5Y@Xu3nFrL_f*jclf zLgev49HhxoMbx0aO7G-CgRjgTu4kND-nlG)R;_mzdJ+7ce}SRxIyItMgrlFXAEQ1& zu~1`WE5-y<=Ji$9E`K~6uG4#Tu!U)w;}*+AhwzJBmzaXAy3?PZC{TE z5a2u>b>@I0ok1mw$``GME)TY8wmJJRa9*f__?Jj&8@lbN@bPOWlN9u&!2G`(HgRba zO}zRQ2sSli{P9OM?R0sn-p)m*?bSGAEUpMti=^}Sy3yg&xj3uGX4;(FNufQwQalack&hIBu|}QwY0;M* zaiK(e-WppPFEmT46AJKiM9S?k#jvsdaVrZ3n$ADSYh0WB+Zwtr%jqCvQWo}Tbcna4Mv`NW#t z_-+lXMU8t?@-ETj$#!9kDIqD=AG_m8#~saaa_&OEta?A22Xn=DMgiY(CZ@N;Anc$? zQ~vH{M?T?CGd>^#M+Of8x9pyEMp3yE@HKDLs+rgjbrRu9MKtI60yMQo1h&)u+Gs@WQD6@aB`u zRB1oVA7!GhSVr#Cz-|;E&{=cSjqATmK%*lcVVZ%5n=J?rzt*T8=UX*3jY*UZ!U< zV>Bey=(-NFeN0#%XW}>Ost>YVf!o&JjQ~9B|13aK64S^x=8S&#l2h+TT0J|DU}je% z^{MRPngXQUPfvKsOA2bLpuiE0+d`BN#7wFF+%-Y5b?2+T9I~%>i(9}>3MIZHZbWGO z*DZ2(1_Z*w1FsD%(VrdsdsNs$IP(YL{X@i+2rV29*8eafZF6k5kjt{@t+K|q(+L*M zU*M0mVeo#Y6Ca(el6WPW)oQ)ZG!Qdo?!U3k1sWlrKDVBMyp9rNkSDaRsLpVHe(9yy zIAofR<)0+u30*v)XKt!0kwe@1N8{HGlu*-qAbs5V`SL*^bEpVd6@!{%d@N_-@SmRV z?;^nm>mu3#s%op6jU%)PDq@3eJN;i#~dTayLLbb_$t17YQ8f=qSmnnI((Ev z-b2eoA;TB22D?bPwzea=;At(!MYYxueTxW;umd>O8(v!yv8SvH@yqn z%`y>^?~wh=2IK&~A`|7EDrko4HIt{QM)$XRc|o0`al5Sgf@qsdo8&aUxrX5JHIuDy zDjcUwX=ooHeW0;=KC&jYR9n{ALZ;VZ>HD`$%6|^L3%+qATL3I%%8VMvsxGK$j@R-= z`G~EsegMEXmBp6M?4V_CxPhN-?NbC0B@cpW&@xdfW@&Esmx<&L%z$}yyN~{OStuR% zKH+c9Irn00&rMz3QQ>+vajZ-efPFggE$J&(AYBS6_aidvt1O%|AeBfHU~FCn{<_ZR zDVl(C{H@D%!}JnB77Mlt@4c=>c4L?C9m64;l1z!?L|$0P4J&P zzUau1CFR4jxNAUkdD|04M+$Uh?`UEi1J)vZK%C*WiIil+VlP=#e;5ME*F1M?vw4MEODnNxqL@U zQ#27Mv#MwW;+lbJkj5=9@1JIE@Z`(fMPM39(y8dZwS=Jhdx6jMZ53_;sWn z#QGsKg3tq#P!a4Xa@;PsTITMv0@l=7M;X;7ZiJS8P4=90`sX!(!Y12!r2+W5k#9{6 zUPo-SjWx|l${$KQaR&MZT(MHKFbt_>C7$F0a1Dg~j_R5e*08tWS@=D6PcFyyM7n04 z66Qbeb|_+tM^EbJ^u!Ro+LDd3dXQ_wO77|+)Vm2k_>x7DP)}>#GQ@$eJf9nQUOR~U zhN1o$?^irKP>6qzTUmtA-9civjXn@|&eApfw?4fxt}L0b3Wy>+!_kAx(Tn)9qiSKS z=rR`CpgFs+xL*x=%l;otR~Z#$|FoCxPLT%bP+FE|5$Q&dQbM|63F(jqX^{p=3F$6r zkdkKUu3Z|W-<#)u-Y=fxe&PPrH8a;-GZU8v{fcZUN}$_%_EGL$Y1B31eTl}FM=Sn^ znC^kB?AE63mZ#ff#f#b{a5!0a|4BU)T(n-#s|;QU_p&gPL^+q<_zwN(Ndmx7XbLY~ z66m<;>_f8OD1StC=lI(_5a>$Job?_>pZP4d{2-W;=P%Xfvka=_y_V%B&x}%KLxE1m zY;#f~q8GALrxd!=5QHQy@RF4`QD0MZt@cVy1b_z!Exv;!1>cTDFhFC0_fcos4>|jU z;yS$$$sn8yjTn!A0a3?)xOlep{j<pkX`$G9(+$kvTV_p7)K(+@j;V>YpU zHThT|A6gGvQ;%7z{y-us^h!4+MOv6>!^|R6mutIf^JDZIZ&KU|xnETb z5~(5h5INeKM)qG9R_GJiYI+Y8LEr2X63_qV@gp7QPRqPEm1I8N_(0OkaB=~w{xEKz zZJp2|3m@+1iG)!=Ea@f=<$x|GvorGd#vsuJ!Ce@19{BtxTZXx*J=>J$iTFe2x9ARR z{hzd-cY*=8nJ042qt(cgvroTC4CnPGGVB+XsaGBLlO_JtoJp0-+Lt_F1F<%$%@kF7 z&k8RwgcJ)>>4@i22Mo`q+$2LHn-cMf;;T;L6KX&{W3`YI{i4N7f^X1T4QWOXd=M<( zi|k#Th{HowAx#qB`sI}%C;+*?X_R@$yqghcRHOIhaICM`kb-fY{+nUd7Wec(|Es z0&(HtP^P@A`P5zQ4m(J>m)i654;LeX$bVi{V1*KU$2nsZgfnjXSb~wfabrd$d)#?6 zSehRi(J*9#SUxvPtf7fMP1jOMK141B`nDF|3ZiVA}AYW*LtZ=tsBbl zA#_4C154H!`U}m{in}$lZ{jcCE=yr>f`Zbga^ivehi86sPp(~g+5$ezE6eW#&~m|i z)9xz%208BQiy^}z-J7HC5AwzuPq!R;rtEg(ahl^#F>$74NOMKpDBPQ=zft6SH=5^N zeP9?Uu`ilf%>DY8C(BOGS2sdby2@dC;#T;(p9XVV~Q+Zz{4mLL&9GNW4oyV*LM{m%hD=5>^*v6G^P zIf4VO4UYyjjewKq5K#H74NZKeL2YlT16V~I?Wc4)zINa$4V0f2`(vWE8SRwrDpl}D z3~&cNWFIJ_J#3IIGrxhr+U6|%mQQyDOvwDT8y|g{uv{^8)V%re+U9e85%VAynxazH z+#(>ds$+j5iKJFfucsK!a4da?KLSyX{X4CxJ!}oqXS1zKO}Fpv?m5t?To$;IjHu^}dZQ!?fWDdX2E6QJd)J^6ozI2a<`zNJt3{@|rHonycOZ=b1@ zMBo#j(wYc4>9Z98g=aS6_yT__;F|DCVZb7~s7;%;AR^6HzCWY^=HfMJ#h3{a{Wh>y z0OhCg{POUX7J)yD=)D3~kR;pYH@9+GZTT#d?b*VglHgqeBi6`uec+nd$75V)4vP`p z6Qp`UAgFFJj?a*bFGh1Mu2SQ?mK%eir(P?;*+IYEJnWEE+h1$iIB(z-e2u)Drx&U$ zWbvs6K>WSY>}{0~-th{k`A4-!b{PCOk}hRgw>CcMjuqbM3{{AhI5}$z{;|bPg-77} zChxlrNzuVD5~7I;@9!@-J+CB)EaxE z6t4N}n$+-xihbk`rT{o;1+1T(yEh~k+_o!Vt=H#~q0M;`8pkAKHOP5k=W7^JA9 zki$aV5S=~=;f@rJ7DQ)a=nSLSGk#k*DTNCao>^%eW437#bG~WiIJ8%Q&};(~hrVCe z@7RFlPfH}l%hpTMpBbq@T4e^>&e;I^)*~2XBPE++Ne3Dn!zF4vfVbmDNu22~5sKw} zILI^aIrkF+BtonLqz`Z9$Rv23NGz~;iRwj8WfmIFuz`?{E#}DwHlWRfj_}7@cf4hG zk$kty$i@Ygw-wT9yEu(h@K?SPtqy8@^kC(9Ja|HBtqbav9#TEAN1!Oi)kfBR18ENb zH=8kA3H4Q}HHP3`SL8%T==<#DT=~kS@hvJCe)W&eBV5zAMj=D1uS!XpOtIc!YzB(;E?H4?$oG0V}nGdH7 zaCzr7l+FMwjVsGQ5-DgD$YXJ(8~7b44&y7125;q?pd3FR#z(`)S-@c80uPLKljTc% zSy$QV3Od50d)a?p%L6ol!nrey*%CupHP|y&!l|E{ECbBZyGWU-4aKE!*&6e136)d9 zpX3DBSeKI_S5a~(cb5upfQzq^1+9*LH-J9u!^oIQ+Kw^ZUg3&D;O_w-MLonKS(*#j zbH2rziL@{vs`P%~kKglOfH5kWLavz6H?z+eES8vAxFpnjURSoC~_5^ca_Ti~0>j|Lag4Xv=*& zD$wR92i5K|rRR_K31L5Mn=sOe| zai_Syan21bd5kvpQ551$uZ{mvXA$3$MVjwB9 z{l)&>^WP60w5ts}s#=ixluUsJqvFB`dV0Sf7)JwAPA^F@yuLD#c^|ue^AjPZ0Xf3> z85ABuLb;CPQBUusr#*bXB0|j>W?SNB4pP4G0Vy6-k)?Tj5HjzM6^I(B7G01hxk)|8 zM{ZkgEJPYe1Mt~?; zMY1iG>rS_MRc>|z;<%kQVQG-^(X;#e3S%*#0LK^u>qjE%2|i(^Z9vX*dLy*Icv8Yr zEga)`K^3rvXou1{HJf_qTuQC|H`V%%vbqhH{Jt@HE3yZMz^h>CX%Y#=E1Bw%CxU(a zS9|5gTnB;i#_a1+W=2qiXX64+f5sNpI0Y!qmj&3Xh>&I0Aj4sNB--yruM&h4%yUAw z8Xh)MtOm6L3~lu$1%)@4us>9P#CWE6j3~bOIPup6xpBe0J{%YA<&Zm1ysd21wow5` z&174wqs>IrQt1sR(b?I?WGRJdqq3P^gin$JxaYZ-@(r{t5&jubP$tYucxU=?Et+o8 zP;gC60YXev_Y66Nn^yqsc(Lt90b;I<+J=YVUn8o9=&gZ#){jAhq4!8v1-BB>7s%D`L5|`#X*R zSVQt49pP5*CF>J3ph9LBLgbK-tB;Z@^!P{HkL@XAzPu?63dNxktHWUECe9mQ&D1$pA0 zXz_Y<4tXkix|0*h6|d#wa7IgI-AQUy$4Mh?*6uI?HUExNlpxTFoGHF@Yr?7pivgH8 z2uV`$Zo${al5hh|P01SCjoNJ5enhW2FJP0LLzOA@UY~H_FVsbc->x9#<|YHo)~+qK z(FTVSYFOiwEx~c%&G;;)&GWM2nj?O^sgGt;ij7(sMcsCup%bqGLP7m;UK(-Dc_%_T zmL3unORcwRxk*n_XD^#q5Q@1eX9pf+V5;-flnCH)t@>(LElJ$SR@^n;jAlg0{2043 zcgi-O+xo{R;~YDbq{Ay`<(yWb54cy*!ft5bDWyK+5C^*K%bTiOWkyB3vFl%%sH;)k zkj>wK3aHN_nZy#CIUSKb@-}b(rf(7ASCnA`z+)|m#t}-GGY0q%@c9=#vab%~dee0@ z#U0w5F}^yjIK*O;!Z7hyhTxxg!2RF0 zl)_rixr*CYM|Z}7yoOvXEo?VHL_i?41{?!iuxWOK&(*17J{D^$#P8OrYY`!I0)dg;)PxLB|~n>`J6h{(AP2hEXvF1W>yAYWPlB3h0d}z+CtL^#y>~M<6$gR5FmZ zm3jX?*fUnP&HcI@%OpvCzT<2F#LyF?sM%m;u+>u|SycwOtneCT5i%(AuZ2w%MRV^;}^#+(aCBOQtv`sMN+mscH|>5Os$t zQDJcN>(da>Sd*Tz&}ohc`9-5<$8V3|@&Q}(vJt50Z#W*o2<0+ZV!PMaz-du=x$X_b zK3(xQpD}Sgcn6@D|5k0UW(A10bH3ZpCwq9`0Jxoe*jL@q($>AG@b3q{c;~ml9ISDT zt8lPT(dSp?(XdRo_KNd&c}UE-|7%>FN-tNHJ`++V#vxRWF5ClrqDA4Tj-yY>VH}PN zpf0hE?}VuBz*zw-K#ra{ssCGt)EHqv*73sL(_C5-$@mR9?gA427dWU>OCQ0=njVTo z9VTk{%&zC;{p{}e(z5wzg-49Zb9=@kU@n@m0deMtnNN7}h$eyau#Z|0^^*(sZhbAJ zmn2uM_lB07kuP68DwF*+fE>3R3||wVO?dIcrK}wZh~8RF)iAjBliFjuXX_WusaRzy zko3u$*}dGomqd0-a2^SBH;{>n0S^T9xo-ddr!7tP%+s}Y6P7|_PA`1P79bjBrIY4l z^q2rhRv?3v#N6E4SP%4_f#@f6NS@Q~#fcwVc&69LKoYL;$#CE(q#M>tKQO7zyswoV z-`wE0?5yaGeQWh=o%P<5W=rCGINO<59~g%A)$qSXh{fOInHtgB0|IUGu`!Q!{sNG= zA&_AGdFd~`iU&Mx=f!IaKl^5iNviwWxRqy8tf^^?H(9_E7K~Q^nfl2jv91DztzBV9 zNG@?+@%!R9%QQNU%d(^5dcLkP*q@%whRY!M5%m4!sWFel0n;pcw_8@>k1Fc#apSnc zDQRg8cF>kzfHFEH;G1tn1L?|U2ghWbqM}-7=rJP0XY#X!a;UtDk58+}ki(^PXy_hn ztJ*ql*BjtvJ>-`D|*fb$tQn-N~cJU^^}!Gv_+ufZ*_>bIK6`-oUr$uDoO-f z!6?U1*qu#}5(u+tNIU1Av+e$lfA$}X$2qXl6Z8Nux{j)g9|k{MabXL-bGL1roWRP| z`@-b0>NQbIo5hjZ;?jBT_SO8o>$hhILxorIq!-L*5)K_M;z05{5O`95X5Q}5q3k*u zp2fwA3vmk~=w<7kw{RVfdu>pH!t~tPoDebb*F}=ruOr8jtHWhzNq47(*1R{q=3U20 zXiC@=C`f29eR;Fv|BmR6s?(4B8oSLxV|t(iFDgZJ3|d`71M+0>{iCC6n}8zcKeaZr z%9&AOFs_og@{jG>Xg?tx@pb|j#~y+1|Daρa6NC?U$H1 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 - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - CoRelAy - - - - - - - - diff --git a/docs/source/_static/favicon.svg b/docs/source/_static/favicon.svg index db7c74c..e6484d9 100644 --- a/docs/source/_static/favicon.svg +++ b/docs/source/_static/favicon.svg @@ -1,170 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 1a2fa58..76cb217 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -2,6 +2,9 @@ CoRelAy Documentation ===================== +.. image:: ../../design/corelay-logo-with-title.png + :alt: CoRelAy Logo + CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines. It was created to swiftly implement pipelines to generate analysis data which can then be visualized using ViRelAy. From 662d04111d8d1a6bdccb5a0cc69337672d80952d Mon Sep 17 00:00:00 2001 From: David Neumann Date: Wed, 16 Apr 2025 13:12:07 +0200 Subject: [PATCH 05/19] Removed the LICENSE file The project is dual-licensed under the GNU General Public License Version 3 (GPL-3.0) or later, and the GNU Lesser General Public License Version 3 (LGPL-3.0) or later. The GPL-3.0 license is in the COPYING file and the LGPL-3.0 license is in the COPYING.LESSER file. Additionally, there is LICENSE file, which contains a note about the dual-licensing. This is, however, confusing, as GitHub does not recognize that the file is only a note about the dual-licensing and not the actual license. This commit removes the LICENSE file and adds a note about the dual-licensing to the read me. Also, the read me was cleaned up a bit to fix some of the problems that Markdownlint found. --- LICENSE | 3 --- README.md | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 201d49d..0000000 --- a/LICENSE +++ /dev/null @@ -1,3 +0,0 @@ -* CoRelAy is licensed under the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3 OR - LATER -- see the 'COPYING' and 'COPYING.LESSER' files in the root directory for - details. diff --git a/README.md b/README.md index 42ddbb8..0fcba98 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,9 @@ [![PyPI Version](https://img.shields.io/pypi/v/corelay)](https://pypi.org/project/corelay/) [![License](https://img.shields.io/pypi/l/corelay)](https://github.com/virelay/corelay/blob/main/COPYING.LESSER) -CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines. -Pipelines are designed with a number of steps (Task) with default operations (Processor). -Any step of the pipeline may then be individually changed by assigning a new operator (Processor). -Processors have Params which define their operation. +CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (Task) with default operations (Processor). Any step of the pipeline may then be individually changed by assigning a new operator (Processor). Processors have Params which define their operation. -CoRelAy was created to quickly implement pipelines to generate analysis data -which can then be visualized using ViRelAy. +CoRelAy was created to quickly implement pipelines to generate analysis data which can then be visualized using ViRelAy. If you find CoRelAy useful for your research, why not cite our related [paper](https://arxiv.org/abs/2106.13200): @@ -32,22 +28,25 @@ If you find CoRelAy useful for your research, why not cite our related [paper](h ``` ## Documentation -The latest documentation is hosted at -[corelay.readthedocs.io](https://corelay.readthedocs.io/en/latest/). +The latest documentation is hosted at [corelay.readthedocs.io](https://corelay.readthedocs.io/en/latest/). ## Install + CoRelAy may be installed using pip with + ```shell $ pip install corelay ``` To install optional HDBSCAN and UMAP support, use + ```shell $ pip install corelay[umap,hdbscan] ``` ## Usage + Examples to highlight some features of **CoRelAy** can be found in `example/`. We mainly use HDF5 files to store results. The structure used by **ViRelAy** is documented in the **ViRelAy** @@ -146,3 +145,7 @@ def main(): if __name__ == '__main__': main() ``` + +## License + +CoRelAy is dual-licensed under the [GNU General Public License Version 3 (GPL-3.0)](https://www.gnu.org/licenses/gpl-3.0.html) or later, and the [GNU Lesser General Public License Version 3 (LGPL-3.0)](https://www.gnu.org/licenses/lgpl-3.0.html) or later. For more information see the [GPL-3.0](https://github.com/virelay/corelay/blob/main/COPYING) and [LGPL-3.0](https://github.com/virelay/corelay/blob/main/COPYING.LESSER) license files. From b4f9ca6ff14a8b146ad233806b7c5adfca6f51a2 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Wed, 16 Apr 2025 13:23:04 +0200 Subject: [PATCH 06/19] Added a CITATION.cff Added a CITATION.cff file, which contains the necessary information to cite this repository. This file is based on the Citation File Format (CFF) standard. This file is supported by GitHub and results in a "Cite this repository" button on the website, which allows users to directly generate a proper citation for the repository in multiple different formats. Also added a bullet point to the changelog about the removal of the LICENSE file, which I had forgotten to do in the last commit. --- CHANGELOG.md | 2 ++ CITATION.cff | 67 +++++++++++++++++++++++++++++++++++++++ tests/cspell/.cspell.json | 2 ++ 3 files changed, 71 insertions(+) create mode 100644 CITATION.cff diff --git a/CHANGELOG.md b/CHANGELOG.md index ae2c0f4..0c66ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Named and cleaned up all objects and groups in the SVG. - A PNG version of the logo without the title was also added. - All references to the old logo were updated to point to the new location. The URL used in the read me was made absolute, because the read me is also used for the PyPI package and PyPI would not be able to resolve the relative URL to the logo on GitHub. +- The project is dual-licensed under the GNU General Public License Version 3 (GPL-3.0) or later, and the GNU Lesser General Public License Version 3 (LGPL-3.0) or later. The GPL-3.0 license is in the `COPYING` file and the LGPL-3.0 license is in the `COPYING.LESSER` file. Additionally, there used to be a `LICENSE` file, which contained a note about the dual-licensing. This was, however, confusing, as GitHub does not recognize that the file is only a note about the dual-licensing and not the actual license. The `LICENSE` file was removed and the note about the dual-licensing was added to the read me. +- Added a `CITATION.cff` file, which contains the necessary information to cite this repository. This file is based on the [Citation File Format (CFF)](https://citation-file-format.github.io) standard. This file is supported by GitHub and results in a "Cite this repository" button on the website, which allows users to directly generate a proper citation for the repository in multiple different formats. ### Documentation Updates in v0.3.0 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..9e86639 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,67 @@ + +cff-version: 1.2.0 +title: >- + Software for Dataset-wide XAI: From Local Explanations to + Global Insights with Zennit, CoRelAy, and ViRelAy +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - given-names: Christopher J. + family-names: Anders + orcid: 'https://orcid.org/0000-0003-3295-8486' + - given-names: David + family-names: Neumann + orcid: 'https://orcid.org/0000-0003-1907-8329' + - given-names: Wojciech + family-names: Samek + orcid: 'https://orcid.org/0000-0002-6283-3265' + - given-names: Klaus-Robert + family-names: Müller + orcid: 'https://orcid.org/0000-0002-3861-7685' + - given-names: Sebastian + family-names: Lapuschkin + orcid: 'https://orcid.org/0000-0002-0762-7258' +identifiers: + - type: doi + value: 10.48550/arXiv.2106.13200 + description: arXiv Preprint + - type: url + value: 'https://arxiv.org/abs/2106.13200' + description: arXiv Preprint +repository-code: 'https://github.com/virelay/corelay.git' +url: 'https://corelay.readthedocs.io/en/latest/' +abstract: >- + Deep Neural Networks (DNNs) are known to be strong + predictors, but their prediction strategies can rarely be + understood. With recent advances in Explainable Artificial + Intelligence (XAI), approaches are available to explore + the reasoning behind those complex models' predictions. + Among post-hoc attribution methods, Layer-wise Relevance + Propagation (LRP) shows high performance. For deeper + quantitative analysis, manual approaches exist, but + without the right tools they are unnecessarily labor + intensive. In this software paper, we introduce three + software packages targeted at scientists to explore model + reasoning using attribution approaches and beyond: (1) + Zennit - a highly customizable and intuitive attribution + framework implementing LRP and related approaches in + PyTorch, (2) CoRelAy - a framework to easily and quickly + construct quantitative analysis pipelines for dataset-wide + analyses of explanations, and (3) ViRelAy - a + web-application to interactively explore data, + attributions, and analysis results. With this, we provide + a standardized implementation solution for XAI, to + contribute towards more reproducibility in our field. +keywords: + - Explainable Artificial Intelligence + - XAI + - Layer-Wise Relevance Propagation + - LRP + - Spectral Relevance Analysis + - SpRAy + - Zennit + - CoRelAy + - ViRelAy +license: AGPL-3.0-or-later diff --git a/tests/cspell/.cspell.json b/tests/cspell/.cspell.json index 5727f3a..5a547a7 100644 --- a/tests/cspell/.cspell.json +++ b/tests/cspell/.cspell.json @@ -87,6 +87,7 @@ "basepython", "bcde", "bibfiles", + "CFF", "Chormai", "chr5tphr", "chrstphr", @@ -160,6 +161,7 @@ "nosignatures", "notest", "nsamples", + "orcid", "overgeneral", "parseable", "Pattarawat", From 372272bbe8ccb0d36f1058e2b4c49cef1fd39ac9 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Wed, 16 Apr 2025 15:59:23 +0200 Subject: [PATCH 07/19] Converted the Project to a uv project Converted the CoRelAy project from a setup.py project to a uv project. For this, a pyproject.toml file was created, which is configured to do the same as the setup.py file. The source code was moved from "src/corelay" to "source/corelay". The tox configuration was updated and now uses tox-uv to run all commands via uv instead of directly creating environments. This means, that all Python environments can now be run without having to install multiple Python versions. The configuration was also cleaned up. Support for Python 3.7 was removed, not only because it has already reached its end-of-life, but also because some of the dependencies (especially tox-uv) do not support it anymore. The two remaining supported Python versions (3.8 and 3.9) are now recorded in the ".python-versions" file, which makes it trivial to install them using uv. The GitHub Actions workflow that runs the unit tests, linters, and builds the documentation was updated to point to the new locations of the source code, unit tests, and configuration files. Also, it now uses uv to run the tests and linters. Split up the GitHub Actions workflow job that ran PyLint and Flake8 into two separate jobs, which allows for better parallelization and faster execution of the workflow. the "actions/checkout" action used in the GitHub Actions workflow was updated from version 2 to version 4. The GitHub Actions workflow matrix configurations for Python 3.7 was removed, as it is no longer supported by the project. Finally, the workflow was cleaned up and documented. The usage of uv still needs to be documented, but this will be done later when the entire documentation will be updated. Furthermore, the following changes were made: - The unit tests were moved from the "tests" folder to "tests/unit_tests", which clears up space for other test files. - The tox configuration was moved from the root directory to the "tests" directory, which is more appropriate, as tox is mostly used for testing. - The configurations for the linters PyLint and Flake8 were moved from the root directory of the repository (in the case of PyLint) and from the tox configuration file (in the case of Flake8) into the "tests/linters" directory. The CSpell configuration was also moved there. --- .github/workflows/tests.yml | 168 +- .gitignore | 4 +- CHANGELOG.md | 138 +- setup.py | 55 - source/.python-versions | 2 + {src => source}/corelay/__init__.py | 0 {src => source}/corelay/base.py | 0 {src => source}/corelay/io/__init__.py | 0 {src => source}/corelay/io/hashing.py | 0 {src => source}/corelay/io/storage.py | 0 {src => source}/corelay/pipeline/__init__.py | 0 {src => source}/corelay/pipeline/base.py | 0 {src => source}/corelay/pipeline/spectral.py | 0 {src => source}/corelay/plugboard.py | 0 {src => source}/corelay/processor/__init__.py | 0 {src => source}/corelay/processor/affinity.py | 0 {src => source}/corelay/processor/base.py | 0 .../corelay/processor/clustering.py | 0 {src => source}/corelay/processor/distance.py | 0 .../corelay/processor/embedding.py | 0 {src => source}/corelay/processor/flow.py | 0 .../corelay/processor/laplacian.py | 0 .../corelay/processor/preprocessing.py | 0 {src => source}/corelay/tracker.py | 0 {src => source}/corelay/utils.py | 0 source/corelay/version.py | 21 + source/pyproject.toml | 127 + source/uv.lock | 3170 +++++++++++++++++ tests/linters/.flake8 | 16 + pylintrc => tests/linters/.pylintrc | 0 tests/{ => linters}/cspell/.cspell.json | 2 + tests/{ => linters}/cspell/package-lock.json | 0 tests/{ => linters}/cspell/package.json | 2 +- tests/tox.ini | 98 + tests/{ => unit_tests}/__init__.py | 0 tests/{ => unit_tests}/corelay/__init__.py | 0 tests/{ => unit_tests}/corelay/io/__init__.py | 0 .../corelay/io/test_storage.py | 0 .../corelay/pipeline/__init__.py | 0 .../corelay/pipeline/test_base.py | 0 .../corelay/pipeline/test_spectral.py | 0 .../corelay/processor/__init__.py | 0 .../corelay/processor/test_base.py | 0 .../corelay/processor/test_clustering.py | 0 .../corelay/processor/test_embedding.py | 0 .../corelay/processor/test_flow.py | 0 .../corelay/processor/test_preprocessing.py | 0 tests/{ => unit_tests}/corelay/test_base.py | 0 .../corelay/test_plugboard.py | 0 .../{ => unit_tests}/corelay/test_tracker.py | 0 tests/{ => unit_tests}/corelay/test_utils.py | 0 tox.ini | 84 - 52 files changed, 3630 insertions(+), 257 deletions(-) delete mode 100644 setup.py create mode 100644 source/.python-versions rename {src => source}/corelay/__init__.py (100%) rename {src => source}/corelay/base.py (100%) rename {src => source}/corelay/io/__init__.py (100%) rename {src => source}/corelay/io/hashing.py (100%) rename {src => source}/corelay/io/storage.py (100%) rename {src => source}/corelay/pipeline/__init__.py (100%) rename {src => source}/corelay/pipeline/base.py (100%) rename {src => source}/corelay/pipeline/spectral.py (100%) rename {src => source}/corelay/plugboard.py (100%) rename {src => source}/corelay/processor/__init__.py (100%) rename {src => source}/corelay/processor/affinity.py (100%) rename {src => source}/corelay/processor/base.py (100%) rename {src => source}/corelay/processor/clustering.py (100%) rename {src => source}/corelay/processor/distance.py (100%) rename {src => source}/corelay/processor/embedding.py (100%) rename {src => source}/corelay/processor/flow.py (100%) rename {src => source}/corelay/processor/laplacian.py (100%) rename {src => source}/corelay/processor/preprocessing.py (100%) rename {src => source}/corelay/tracker.py (100%) rename {src => source}/corelay/utils.py (100%) create mode 100644 source/corelay/version.py create mode 100644 source/pyproject.toml create mode 100644 source/uv.lock create mode 100644 tests/linters/.flake8 rename pylintrc => tests/linters/.pylintrc (100%) rename tests/{ => linters}/cspell/.cspell.json (99%) rename tests/{ => linters}/cspell/package-lock.json (100%) rename tests/{ => linters}/cspell/package.json (76%) create mode 100644 tests/tox.ini rename tests/{ => unit_tests}/__init__.py (100%) rename tests/{ => unit_tests}/corelay/__init__.py (100%) rename tests/{ => unit_tests}/corelay/io/__init__.py (100%) rename tests/{ => unit_tests}/corelay/io/test_storage.py (100%) rename tests/{ => unit_tests}/corelay/pipeline/__init__.py (100%) rename tests/{ => unit_tests}/corelay/pipeline/test_base.py (100%) rename tests/{ => unit_tests}/corelay/pipeline/test_spectral.py (100%) rename tests/{ => unit_tests}/corelay/processor/__init__.py (100%) rename tests/{ => unit_tests}/corelay/processor/test_base.py (100%) rename tests/{ => unit_tests}/corelay/processor/test_clustering.py (100%) rename tests/{ => unit_tests}/corelay/processor/test_embedding.py (100%) rename tests/{ => unit_tests}/corelay/processor/test_flow.py (100%) rename tests/{ => unit_tests}/corelay/processor/test_preprocessing.py (100%) rename tests/{ => unit_tests}/corelay/test_base.py (100%) rename tests/{ => unit_tests}/corelay/test_plugboard.py (100%) rename tests/{ => unit_tests}/corelay/test_tracker.py (100%) rename tests/{ => unit_tests}/corelay/test_utils.py (100%) delete mode 100644 tox.ini diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4dd3d87..6463eb5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,102 +1,150 @@ -name: tests + +# This workflow will run the unit tests, the linters, the static type checker, the spell checker, and will build the documentation +name: CoRelAy Continuous Integration + +# This workflow runs when commits are pushed to the main/develop branch or when a pull request for the main/develop branch is opened or pushed to on: push: - branches: [main] + branches: + - main + # - develop pull_request: - branches: [main] + branches: + - main + # - develop +# This workflow contains multiple jobs for unit testing, linting, type checking, spell checking, and building jobs: - test: - name: test ${{matrix.tox_env}} + + # Runs the unit tests on Python 3.8 and 3.9 + unit-tests: + + # The job will run on the latest version of Ubuntu using a matrix strategy, where the unit tests are run using Python 3.10, 3.11, 3.12, and 3.13 + name: Unit Test CoRelAy on Python ${{matrix.python-version}} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - - tox_env: py37 - python: "3.7" - - tox_env: py38 - python: "3.8" - - tox_env: py39 - python: "3.9" + - tox-environment: py38 + python-version: "3.8.20" + - tox-environment: py39 + python-version: "3.9.21" + + # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct + # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and finally 5) running the unit tests steps: - - uses: actions/checkout@v2 + - name: Checkout Repository + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Install base python for tox - uses: actions/setup-python@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - python-version: "3.9" - - name: Install tox - run: python -m pip install tox - - name: Install python for test - uses: actions/setup-python@v2 + version: 0.6.14 + - name: Install Python + run: uv python install ${{ matrix.python-version }} + - name: Install CoRelAy and its Dependencies + run: uv --directory source sync --all-extras --dev + - name: Run Unit Tests + run: uv --directory source run tox --conf ../tests/tox.ini run -e ${{ matrix.tox-environment }} + + # Runs the PyLint linter + pylint: + + # The job will run on the latest version of Ubuntu + name: Lint CoRelAy Using PyLint + runs-on: ubuntu-latest + + # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct + # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and 5) running the PyLint linter + steps: + - uses: actions/checkout@v4 with: - python-version: ${{ matrix.python }} - - name: Setup test environment - run: tox -vv --notest -e ${{ matrix.tox_env }} - - name: Run test - run: tox --skip-pkg-install -e ${{ matrix.tox_env }} + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.6.14 + - name: Install Python + run: uv python install 3.13.2 3.9.21 + - name: Install CoRelAy and its Dependencies + run: uv --directory source sync --all-extras --dev + - name: Run PyLint Linter + run: uv --directory source run tox --conf ../tests/tox.ini run -e pylint + # Runs the Flake8 linter + flake8: - check: - name: check ${{ matrix.tox_env }} + # The job will run on the latest version of Ubuntu + name: Lint CoRelAy Using Flake8 runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - tox_env: - - flake8 - - pylint + + # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct + # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and 5) running the Flake8 linter steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Install base python for tox - uses: actions/setup-python@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - python-version: "3.9" - - name: Install tox - run: python -m pip install tox - - name: Setup test environment - run: tox -vv --notest -e ${{ matrix.tox_env }} - - name: Run test - run: tox --skip-pkg-install -e ${{ matrix.tox_env }} + version: 0.6.14 + - name: Install Python + run: uv python install 3.13.2 3.9.21 + - name: Install CoRelAy and its Dependencies + run: uv --directory source sync --all-extras --dev + - name: Run Flake8 Linter + run: uv --directory source run tox --conf ../tests/tox.ini run -e flake8 + # Runs the spell checker CSpell to spell-check all files in the repository spell-check: + + # The job will run on the latest version of Ubuntu; usually, when a job fails, the entire workflow will fail, but in this case, we do not want the + # the workflow to fail if the spell-check fails name: Spell-Check Repository runs-on: ubuntu-latest continue-on-error: true + + # The job contains several steps: 1) checking out the repository, 2) installing Node.js, 3) installing the dependencies of the CSpell global + # configuration, and 4) running the CSpell spell checker steps: - name: Checkout Repository uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Install NodeJS + - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 22 + node-version: "22.14.0" - name: Install CSpell Global Configuration - run: npm --prefix tests/cspell install + run: npm --prefix tests/linters/cspell install - name: Run CSpell Spell Checker - run: npm --prefix tests/cspell run cspell + run: npm --prefix tests/linters/cspell run cspell + + # Builds the documentation using Sphinx + build-documentation: - docs: - name: docs + # The job will run on the latest version of Ubuntu; usually, when a job fails, the entire workflow will fail, but in this case, we do not want the + # the workflow to fail if the documentation build fails + name: Build CoRelAy Documentation runs-on: ubuntu-latest - strategy: - fail-fast: false + continue-on-error: true + + # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct + # Python version, 4) installing TeX Live, which is required by Pybtex, a replacement for BibTeX, used by Sphinx to generate the citations in the + # documentation, and 5) building the documentation steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Install base python for tox - uses: actions/setup-python@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - python-version: "3.9" - - name: Install tox - run: python -m pip install tox - - name: Setup test environment - run: tox -vv --notest -e docs - - name: Run test - run: tox --skip-pkg-install -e docs + version: 0.6.14 + - name: Install Python + run: uv python install 3.13.2 3.9.21 + - name: Install TeX Live for Pybtex + run: sudo apt-get update -y && sudo apt-get install -y texlive texlive-latex-extra dvipng + - name: Build Documentation + run: uv --directory source run tox --conf ../tests/tox.ini run --notest -e docs diff --git a/.gitignore b/.gitignore index 558fb30..b67d2d2 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,8 @@ npm-debug.log docs/build docs/doctree docs/source/_generated -docs/source/api-reference/* -!docs/source/api-reference/index.rst +docs/source/reference/* +!docs/source/reference/index.rst # Trained models and datasets/attributions/analyses *.pt diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c66ec8..320a816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,10 @@ ### General Updates in v0.3.0 -- Renamed the `master` branch to `main` in order to avoid any links to sensitive topics. - - All references to the `master` branch in the repository were updated to `main`. +- Renamed the `master` branch to `main` in order to avoid any links to sensitive topics. All references to the `master` branch in the repository were updated to `main`. - Added this changelog, as well as a contributors list, which contains a list of all people that made contributions to the project. - Added a CSpell configuration for spell-checking the contents of the repository, checked all files, and corrected all spelling mistakes. -- Moved the logo and its source file from the docs to a separate top-level `design` directory. +- Moved the logo and its source file from the docs to a separate top-level `design` directory: - The source file was cleaned up: - Converted the title of the logo to a path, because the font is from Google Fonts and not available in the SVG. It would be possible to embed the font, but this would increase the size of the SVG significantly and would require us to include the license. - The logo was previously only available with the title. For this reason a second page was added to the SVG, which contains the logo without the title. @@ -20,6 +19,28 @@ - The project is dual-licensed under the GNU General Public License Version 3 (GPL-3.0) or later, and the GNU Lesser General Public License Version 3 (LGPL-3.0) or later. The GPL-3.0 license is in the `COPYING` file and the LGPL-3.0 license is in the `COPYING.LESSER` file. Additionally, there used to be a `LICENSE` file, which contained a note about the dual-licensing. This was, however, confusing, as GitHub does not recognize that the file is only a note about the dual-licensing and not the actual license. The `LICENSE` file was removed and the note about the dual-licensing was added to the read me. - Added a `CITATION.cff` file, which contains the necessary information to cite this repository. This file is based on the [Citation File Format (CFF)](https://citation-file-format.github.io) standard. This file is supported by GitHub and results in a "Cite this repository" button on the website, which allows users to directly generate a proper citation for the repository in multiple different formats. +### CoRelAy Updates in v0.3.0 + +- Converted the CoRelAy project from a `setup.py` project to a uv project: + - A `pyproject.toml` file was created, which is configured to do the same as the `setup.py` file. + - The source code was moved from `src/corelay` to `source/corelay`. + - The tox configuration was updated and now uses tox-uv to run all commands via uv instead of directly creating environments. This means, that all Python environments can now be run without having to install multiple Python versions. + - The tox configuration was also cleaned up. + - Support for Python 3.7 was removed, not only because it has already reached its end-of-life, but also because some of the dependencies (especially tox-uv) do not support it anymore. The two remaining supported Python versions (3.8 and 3.9) are now recorded in the `.python-versions` file, which makes it trivial to install them using uv. +- The unit tests were moved from the `tests` folder to `tests/unit_tests`, which clears up space for other test files. +- The tox configuration was moved from the root directory to the `tests` directory, which is more appropriate, as tox is mostly used for testing and linting. +- The configurations for the linters PyLint and Flake8 were moved from the root directory of the repository (in the case of PyLint) and from the tox configuration file (in the case of Flake8) into the `tests/linters` directory. The CSpell configuration was also moved there. + +### CI/CD Updates in v0.3.0 + +- The GitHub Actions workflow was updated to point to the new locations of the source code, unit tests, and configuration files. +- Converted the GitHub Actions workflow to use uv to run the tests, linters, and build the documentation. +- The GitHub Actions workflow was cleaned up and documented. +- Split up the GitHub Actions workflow job that ran PyLint and Flake8 into two separate jobs, which allows for better parallelization and faster execution of the workflow. +- Updated the `actions/checkout` action used in the GitHub Actions workflow from version 2 to version 4. +- Removed the GitHub Actions workflow matrix configurations for Python 3.7, as it is no longer supported by the project. +- Added a job to the GitHub Actions workflow, which spell-checks the repository. + ### Documentation Updates in v0.3.0 - The logo was added to the documentation, which previously contained a copy of the logo in the `docs/images` directory, but did not include it. The version contained in the `docs/images` directory was removed and the index page now directly references the logo in the `design` directory. @@ -31,69 +52,76 @@ ### General Updates in v0.2.1 -- Fixed a small typo in the read me by @p16i in #8. -- Clarified the maximum number of neighbors in the `SparseKNN` affinity by replacing the confusing `if`-`else` expression with the `min` function by @chr5tphr in #10. -- Added a new logo, which has a design similar to the logo of ViRelAy and added a link to the documentation, as well as some badges to the read me by @chr5tphr in #12. +- Fixed a small typo in the read me. +- Added a new logo, which has a design similar to the logo of ViRelAy and added a link to the documentation, as well as some badges to the read me. + +### CoRelAy Updates in v0.2.1 + +- Updated the outdated PyLint configuration by removing deprecated disables and fixed tests and PyLint errors: + - Fixed various PyLint style errors, mainly concerning the usage of f-strings instead of `format`. + - Fixed the use of `sklearn.datasets.load_digits`, which now requires keyword-arguments. + - Added `disable=unspecified-encoding` to the PyLint configuration file `pylintrc`, as this rule has a lot of trouble with false-positives. +- Moved the `corelay` package directory to `src/corelay` to fix the coverage report and prevent other pitfalls. +- Clarified the maximum number of neighbors in the `SparseKNN` affinity by replacing the confusing `if`-`else` expression with the `min` function. +- Defined the requirements for docs and tests (mostly) in the `setup.py` using `extras_require` and removed the `docs/requirements.txt`, which is no longer needed. +- Fixed the Docstrings and some NumPyDoc issues: + - Removed trailing "s" after backticks. + - Added missing docstring headers. + - Used double backticks for inline-code. +- Removed the outdated `requirements.txt`, which was supposed to be removed in #13. ### CI/CD Updates in v0.2.1 -- Added a GitHub Actions workflow, which runs the unit tests and builds the documentation by @chr5tphr in #13. - - Fixed tests and PyLint errors - - Fixed various PyLint style errors, mainly concerning the usage of f-strings instead of `format`. - - Fixed the use of `sklearn.datasets.load_digits`, which now requires keyword-arguments. - - Added `disable=unspecified-encoding` to the PyLint configuration file `pylintrc`, as this rule has a lot of trouble with false-positives. - - Added a GitHub Actions workflow. - - Defined the requirements for docs and tests (mostly) in the `setup.py` using `extras_require`. - - Added a GitHub Actions workflow for tox. - - Updated the `.readthedocs.yaml` configuration file to use the `extras_require` "docs" in the `setup.py` - - Removed the `docs/requirements.txt`, which is no longer needed. - - Added a "tests" badge to the read me. - - Moved the documentation from imgmath to MathJax. - - Moved the `corelay` package directory to `src/corelay` to fix the coverage report and prevent other pitfalls. - - Change the linkcode URL in the documentation to correctly point to `src/corelay` on GitHub. - - Updated the outdated PyLint configuration by removing deprecated disables. +- Added a GitHub Actions workflow, which runs the unit tests and builds the documentation. +- Added more Python versions to the tox configuration and integrated the building of the Sphinx documentation: + - Added support for Python 3.7-3.9. + - Moved flake8 to bottom, and only execute it in the Python 3.9 environment. + - Reformated the PyTest command and added configuration for test coverage. + - Added a new docs environment to build the Sphinx documentation. + - Added a new coverage environment. + - Made the PyLint command locations more explicit and only execute it in the Python 3.9 environment. + - Added a PyTest configuration. + - Added the configuration for test coverage. ### Documentation Updates in v0.2.1 -- Added Sphinx-based documentation by @chr5tphr in #11. - - Fixed the Docstrings and some NumPyDoc issues. - - Removed trailing "s" after backticks. - - Added missing docstring headers. - - Used double backticks for inline-code. - - Added Sphinx documentation. - - Added an index page, with a very brief explanation, installation instructions, contents, and a reference to our paper. - - Added an API reference with AutoSummary and AutoDoc. - - Added a getting started section, which briefly and practically demonstrates Params, Processors, Tasks and Pipelines. - - Added a bibliography with reference to our paper. - - Added a favicon.svg, which currently is an "S", referring to the previous name of CoRelAy, which was Sprincl. - - Ignored properties in AutoDoc to avoid doubling for now, since they are all documented as class attributes. - - Added more Python versions to the tox configuration and integrated the building of the Sphinx documentation. - - Added support for Python 3.7-3.9. - - Moved flake8 to bottom, and only execute it in the Python 3.9 environment. - - Reformated the PyTest command and added configuration for test coverage. - - Added a new docs environment to build the Sphinx documentation. - - Added a new coverage environment. - - Made the PyLint command locations more explicit and only execute it in the Python 3.9 environment. - - Added a PyTest configuration. - - Added configuration the configuration for test coverage. - - Added the configuration for Read the Docs: `.readthedocs.yaml`, which includes the build setup required for publishing the documentation to `readthedocs.org`. -- Remove outdated `requirements.txt`, which was supposed to be removed in #13 by @chr5tphr in #14. -- Remove "Sprincl" references, which was still used in two files, although the project has been renamed to CoRelAy by @chr5tphr in #15. +- Migrated the documentation from using imgmath to MathJax. +- Added a "tests" badge to the read me. +- Added Sphinx-based documentation: + - Added an index page, with a very brief explanation, installation instructions, contents, and a reference to our paper. + - Added an API reference with AutoSummary and AutoDoc. + - Added a getting started section, which briefly and practically demonstrates Params, Processors, Tasks and Pipelines. + - Added a bibliography with reference to our paper. + - Added a favicon.svg, which currently is an "S", referring to the previous name of CoRelAy, which was Sprincl. + - Ignored properties in AutoDoc to avoid doubling for now, since they are all documented as class attributes. +- Added the configuration for Read the Docs: `.readthedocs.yaml`, which includes the build setup required for publishing the documentation to `readthedocs.org`. +- Updated the `.readthedocs.yaml` configuration file to use the `extras_require` "docs" in the `setup.py` +- Changed the linkcode URL in the documentation to correctly point to `src/corelay` on GitHub. +- Removed "Sprincl" references, which was still used in two files, although the project has been renamed to CoRelAy. ## v0.2.0 *Released on June 29, 2021.* -- Added a reference to the ClArC paper with a link and BibTeX in the read me by @chr5tphr in #1. -- Fixed submodules not being found and extra install by @chr5tphr in #2. - - Installing `extra_require` packages must be installed somewhat differently than was shown in the read me, this was updated. Submodules of CoRelAy were also not found when directly installing from an URL. Using Setuptools `find_packages` with a specified include fixed this. -- Example fixes by @sebastian-lapuschkin in #3. - - The imports from `sprincl` were fixed to imports from `corelay`. - - SciKit-Image was updated and the usage of the `compare` function from `skimage.measure` was updated to the `structural_similarity` from `skimage.metrics`. -- Added a perplexity parameter to TSNE by @sebastian-lapuschkin in #4. +### General Updates in v0.2.0 + +- Added LGPLv3+ as the license of the project. +- Prepared the project for upload to PyPI. + +### CoRelAy Updates in v0.2.0 + +- Fixed submodules not being found and clarified how the `extra_require` dependencies have to be installed: + - Submodules of CoRelAy were also not found when directly installing from an URL. Using Setuptools `find_packages` with a specified include fixed this. + - Installing `extra_require` packages must be installed somewhat differently than was shown in the read me, this was updated. +- Added a perplexity parameter to TSNE: - `TSNE` now has an optional perplexity parameter. - The default value is set to the same default of that `sklearn.manifold.TSNE` has, which is 30. - Lower perplexity means more focus on local structures, whereas higher perplexity means higher consideration of global structures. -- Updated the paper in the read me from the old paper to the new paper by @lecode-official in #5. -- Added LGPLv3+ as the license of the project by @chr5tphr in #6. -- Prepared the project for upload to PyPI by @chr5tphr in #7. + +### Documentation Updates in v0.2.0 + +- Added a reference to the ClArC paper with a link and BibTeX in the read me. +- Fixed some minor problems in the examples: + - The imports from `sprincl` were fixed to imports from `corelay`. + - SciKit-Image was updated and the usage of the `compare` function from `skimage.measure` was updated to the `structural_similarity` from `skimage.metrics`. +- Updated the paper in the read me from the old paper to the new paper. diff --git a/setup.py b/setup.py deleted file mode 100644 index df6c8e4..0000000 --- a/setup.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -from setuptools import setup, find_packages - - -with open('README.md', 'r', encoding='utf-8') as fd: - long_description = fd.read() - - -setup( - name='corelay', - use_scm_version=True, - author='chrstphr', - author_email='corelay@j0d.de', - description='Quickly compose single-machine analysis pipelines.', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/virelay/corelay', - packages=find_packages(where='src', include=['corelay*']), - package_dir={'': 'src'}, - install_requires=[ - 'h5py>=2.9.0', - 'matplotlib>=3.0.3', - 'numpy>=1.16.3', - 'scikit-learn>=0.20.3', - 'scipy>=1.2.1', - 'Click>=7.0', - 'scikit-image>=0.18.0', - 'metrohash-python>=1.1.3.post2', - ], - setup_requires=[ - 'setuptools_scm', - ], - extras_require={ - 'umap': ['umap-learn>=0.3.9'], - 'hdbscan': ['hdbscan>=0.8.22'], - 'docs': [ - 'sphinx-copybutton>=0.4.0', - 'sphinx-rtd-theme>=1.0.0', - 'sphinxcontrib.datatemplates>=0.9.0', - 'sphinxcontrib.bibtex>=2.4.1', - ], - 'tests': [ - 'pytest', - 'pytest-cov', - ], - }, - python_requires='>=3.7', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - ] -) diff --git a/source/.python-versions b/source/.python-versions new file mode 100644 index 0000000..5f2806b --- /dev/null +++ b/source/.python-versions @@ -0,0 +1,2 @@ +3.8.20 +3.9.21 diff --git a/src/corelay/__init__.py b/source/corelay/__init__.py similarity index 100% rename from src/corelay/__init__.py rename to source/corelay/__init__.py diff --git a/src/corelay/base.py b/source/corelay/base.py similarity index 100% rename from src/corelay/base.py rename to source/corelay/base.py diff --git a/src/corelay/io/__init__.py b/source/corelay/io/__init__.py similarity index 100% rename from src/corelay/io/__init__.py rename to source/corelay/io/__init__.py diff --git a/src/corelay/io/hashing.py b/source/corelay/io/hashing.py similarity index 100% rename from src/corelay/io/hashing.py rename to source/corelay/io/hashing.py diff --git a/src/corelay/io/storage.py b/source/corelay/io/storage.py similarity index 100% rename from src/corelay/io/storage.py rename to source/corelay/io/storage.py diff --git a/src/corelay/pipeline/__init__.py b/source/corelay/pipeline/__init__.py similarity index 100% rename from src/corelay/pipeline/__init__.py rename to source/corelay/pipeline/__init__.py diff --git a/src/corelay/pipeline/base.py b/source/corelay/pipeline/base.py similarity index 100% rename from src/corelay/pipeline/base.py rename to source/corelay/pipeline/base.py diff --git a/src/corelay/pipeline/spectral.py b/source/corelay/pipeline/spectral.py similarity index 100% rename from src/corelay/pipeline/spectral.py rename to source/corelay/pipeline/spectral.py diff --git a/src/corelay/plugboard.py b/source/corelay/plugboard.py similarity index 100% rename from src/corelay/plugboard.py rename to source/corelay/plugboard.py diff --git a/src/corelay/processor/__init__.py b/source/corelay/processor/__init__.py similarity index 100% rename from src/corelay/processor/__init__.py rename to source/corelay/processor/__init__.py diff --git a/src/corelay/processor/affinity.py b/source/corelay/processor/affinity.py similarity index 100% rename from src/corelay/processor/affinity.py rename to source/corelay/processor/affinity.py diff --git a/src/corelay/processor/base.py b/source/corelay/processor/base.py similarity index 100% rename from src/corelay/processor/base.py rename to source/corelay/processor/base.py diff --git a/src/corelay/processor/clustering.py b/source/corelay/processor/clustering.py similarity index 100% rename from src/corelay/processor/clustering.py rename to source/corelay/processor/clustering.py diff --git a/src/corelay/processor/distance.py b/source/corelay/processor/distance.py similarity index 100% rename from src/corelay/processor/distance.py rename to source/corelay/processor/distance.py diff --git a/src/corelay/processor/embedding.py b/source/corelay/processor/embedding.py similarity index 100% rename from src/corelay/processor/embedding.py rename to source/corelay/processor/embedding.py diff --git a/src/corelay/processor/flow.py b/source/corelay/processor/flow.py similarity index 100% rename from src/corelay/processor/flow.py rename to source/corelay/processor/flow.py diff --git a/src/corelay/processor/laplacian.py b/source/corelay/processor/laplacian.py similarity index 100% rename from src/corelay/processor/laplacian.py rename to source/corelay/processor/laplacian.py diff --git a/src/corelay/processor/preprocessing.py b/source/corelay/processor/preprocessing.py similarity index 100% rename from src/corelay/processor/preprocessing.py rename to source/corelay/processor/preprocessing.py diff --git a/src/corelay/tracker.py b/source/corelay/tracker.py similarity index 100% rename from src/corelay/tracker.py rename to source/corelay/tracker.py diff --git a/src/corelay/utils.py b/source/corelay/utils.py similarity index 100% rename from src/corelay/utils.py rename to source/corelay/utils.py diff --git a/source/corelay/version.py b/source/corelay/version.py new file mode 100644 index 0000000..246de05 --- /dev/null +++ b/source/corelay/version.py @@ -0,0 +1,21 @@ +# file generated by setuptools-scm +# don't change, don't track in version control + +__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '0.2.2.dev7+ga262299.d20250417' +__version_tuple__ = version_tuple = (0, 2, 2, 'dev7', 'ga262299.d20250417') diff --git a/source/pyproject.toml b/source/pyproject.toml new file mode 100644 index 0000000..833887e --- /dev/null +++ b/source/pyproject.toml @@ -0,0 +1,127 @@ + +# The project's basic metadata and dependencies +[project] + +# General project information, the Project supports Python 3.7, 3.8, and 3.9; the version of the project is automatically determined from the latest +# Git tag using the Hatch-VCS plugin and the read me is dynamically generated using the Hatch Fancy PyPI Readme plugin (this is necessary, because +# Hatch cannot access the root directory of the repository while building the wheel, which is where the read me is located; Hatch first builds the +# source distribution from the files in the project directory, from where it can access top-level repository files, and then builds the wheel from the +# source distribution by extracting it into a temporary directory, from where it cannot access the top-level repository files) +name = "corelay" +description = """\ + CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines to generate analysis data which can then be visualized using + ViRelAy.\ + """ +keywords = [ + "CoRelAy", + "Spectral Relevance Analysis", + "SpRAy", + "Explainable AI", + "XAI", + "Interpretability", + "Machine Learning", + "Deep Learning", + "Artificial Intelligence" +] +license = "GPL-3.0-or-later AND LGPL-3.0-or-later" +requires-python = ">=3.8" +dynamic = [ + "readme", + "version" +] + +# The maintainers (people actively working on the project) and authors (people who have contributed to the project) +maintainers = [ + {name = "Christopher J. Anders", email = "contact@cjanders.de"}, + {name = "David Neumann", email = "david.neumann@lecode.de"} +] +authors = [ + {name = "Christopher J. Anders", email = "contact@cjanders.de"}, + {name = "David Neumann", email = "david.neumann@lecode.de"}, + {name = "Sebastian Lapuschkin", email = "sebastian.lapuschkin@hhi.fraunhofer.de"}, + {name = "Pattarawat Chormai", email = "pat.chormai@gmail.com"} +] + +# The project's dependencies, which are included in the requirements of the published package +dependencies = [ + "Click>=7.0,<8.0.0", + "h5py>=3.0.0,<4.0.0", + "matplotlib>=3.0.3,<4.0.0", + "metrohash-python>=1.1.3.post2,<2.0.0", + "numpy>=1.16.3,<2.0.0", + "scikit-image>=0.18.0,<1.0.0", + "scikit-learn>=0.20.3,<1.0.0", + "scipy>=1.2.1,<2.0.0" +] + +# The PyPI classifiers that describe the project +classifiers = [ + "Natural Language :: English", + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Typing :: Typed" +] + +# The URLs to the documentation, the repository, the issue tracker, and the changelog +[project.urls] +Documentation = "https://corelay.readthedocs.io/en/latest/" +Repository = "https://github.com/virelay/corelay.git" +Issues = "https://github.com/virelay/corelay/issues" +Changelog = "https://github.com/virelay/corelay/blob/main/CHANGELOG.md" + +# Configuration specified to the UV Python package manager +[tool.uv] + +# Dependencies that are only installed during the development of the project and are not included in the requirements of the published package +dev-dependencies = [ + + # Dependencies required for building the documentation + "sphinx>=4.2.0,<5.0.0", + "sphinx-copybutton>=0.4.0,<1.0.0", + "sphinx-rtd-theme>=1.0.0,<2.0.0", + "sphinxcontrib.bibtex>=2.4.1,<3.0.0", + "sphinxcontrib.datatemplates>=0.9.0,<1.0.0", + + # Dependencies required for testing + "tox>=4.12.1,<5.0.0", + "tox-uv>=1.0.0,<2.0.0", + "coverage>=5.5.0,<6.0.0", + "pytest-cov>=2.12.1,<3.0.0", + "pytest>=6.2.5,<7.0.0", + "setuptools>=58.1.0,<59.0.0", + + # Dependencies required for linting + "flake8>=3.9.2,<4.0.0", + "pylint>=2.17.7,<3.0.0" +] + +# Configures which build system is used by UV to build the project +[build-system] +requires = [ + "hatchling>=0.8.0,<1.0.0", + "hatch-vcs>=0.1.0,<1.0.0", + "hatch-fancy-pypi-readme>=22.1.0,<23.0.0" +] +build-backend = "hatchling.build" + +# Configures the Hatch-VCS plugin that is used to dynamically determine the version of the project based on the current Git tag +[tool.hatch.version] +source = "vcs" +fallback-version = "0.0.0" +raw-options = { root = ".." } + +# Configures the Hatch-VCS build hook, which is used to write the version of the project to the version file +[tool.hatch.build.hooks.vcs] +version-file = "corelay/version.py" + +# Configures the Hatch Fancy PyPI Readme plugin to compose the content of the read me file from the project's read me file, which is located in the +# root directory of the repository +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "../README.md" diff --git a/source/uv.lock b/source/uv.lock new file mode 100644 index 0000000..92feed7 --- /dev/null +++ b/source/uv.lock @@ -0,0 +1,3170 @@ +version = 1 +revision = 1 +requires-python = ">=3.8" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", + "python_full_version < '3.9'", +] + +[[package]] +name = "alabaster" +version = "0.7.13" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/94/71/a8ee96d1fd95ca04a0d2e2d9c4081dac4c2d2b12f7ddb899c8cb9bfd1532/alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2", size = 11454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/88/c7083fc61120ab661c5d0b82cb77079fc1429d3f913a456c1c82cf4658f7/alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3", size = 13857 }, +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, +] + +[[package]] +name = "astroid" +version = "2.15.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lazy-object-proxy" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/3d/c18b0854d0d2eb3aca20c149cff5c90e6b84a5366066768d98636f5045ed/astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a", size = 344362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/b6/c0b5394ec6149e0129421f1a762b805e0e583974bc3cd65e3c7ce7c95444/astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c", size = 278329 }, +] + +[[package]] +name = "atomicwrites" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/c6/53da25344e3e3a9c01095a89f16dbcda021c609ddb42dd6d7c0528236fb2/atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11", size = 14227 } + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/10/bd/6517ea94f2672e801011d50b5d06be2a0deaf566aea27bcdcd47e5195357/charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", size = 195653 }, + { url = "https://files.pythonhosted.org/packages/e5/0d/815a2ba3f283b4eeaa5ece57acade365c5b4135f65a807a083c818716582/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", size = 140701 }, + { url = "https://files.pythonhosted.org/packages/aa/17/c94be7ee0d142687e047fe1de72060f6d6837f40eedc26e87e6e124a3fc6/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", size = 150495 }, + { url = "https://files.pythonhosted.org/packages/f7/33/557ac796c47165fc141e4fb71d7b0310f67e05cb420756f3a82e0a0068e0/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", size = 142946 }, + { url = "https://files.pythonhosted.org/packages/1e/0d/38ef4ae41e9248d63fc4998d933cae22473b1b2ac4122cf908d0f5eb32aa/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", size = 144737 }, + { url = "https://files.pythonhosted.org/packages/43/01/754cdb29dd0560f58290aaaa284d43eea343ad0512e6ad3b8b5c11f08592/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", size = 147471 }, + { url = "https://files.pythonhosted.org/packages/ba/cd/861883ba5160c7a9bd242c30b2c71074cda2aefcc0addc91118e0d4e0765/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", size = 140801 }, + { url = "https://files.pythonhosted.org/packages/6f/7f/0c0dad447819e90b93f8ed238cc8f11b91353c23c19e70fa80483a155bed/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", size = 149312 }, + { url = "https://files.pythonhosted.org/packages/8e/09/9f8abcc6fff60fb727268b63c376c8c79cc37b833c2dfe1f535dfb59523b/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", size = 152347 }, + { url = "https://files.pythonhosted.org/packages/be/e5/3f363dad2e24378f88ccf63ecc39e817c29f32e308ef21a7a6d9c1201165/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", size = 149888 }, + { url = "https://files.pythonhosted.org/packages/e4/10/a78c0e91f487b4ad0ef7480ac765e15b774f83de2597f1b6ef0eaf7a2f99/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", size = 145169 }, + { url = "https://files.pythonhosted.org/packages/d3/81/396e7d7f5d7420da8273c91175d2e9a3f569288e3611d521685e4b9ac9cc/charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", size = 95094 }, + { url = "https://files.pythonhosted.org/packages/40/bb/20affbbd9ea29c71ea123769dc568a6d42052ff5089c5fe23e21e21084a6/charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", size = 102139 }, + { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, + { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, + { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, + { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, + { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, + { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, + { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, + { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, + { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, + { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, + { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, + { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, + { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "7.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/6f/be940c8b1f1d69daceeb0032fee6c34d7bd70e3e649ccac0951500b4720e/click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", size = 297279 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/3d/fa76db83bf75c4f8d338c2fd15c8d33fdd7ad23a9b5e57eb6c5de26b430e/click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc", size = 82780 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "contourpy" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/7d/087ee4295e7580d3f7eb8a8a4e0ec8c7847e60f34135248ccf831cf5bbfc/contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab", size = 13433167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/7f/c44a51a83a093bf5c84e07dd1e3cfe9f68c47b6499bd05a9de0c6dbdc2bc/contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b", size = 247207 }, + { url = "https://files.pythonhosted.org/packages/a9/65/544d66da0716b20084874297ff7596704e435cf011512f8e576638e83db2/contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d", size = 232428 }, + { url = "https://files.pythonhosted.org/packages/5b/e6/697085cc34a294bd399548fd99562537a75408f113e3a815807e206246f0/contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae", size = 285304 }, + { url = "https://files.pythonhosted.org/packages/69/4b/52d0d2e85c59f00f6ddbd6fea819f267008c58ee7708da96d112a293e91c/contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916", size = 322655 }, + { url = "https://files.pythonhosted.org/packages/82/fc/3decc656a547a6d5d5b4249f81c72668a1f3259a62b2def2504120d38746/contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0", size = 296430 }, + { url = "https://files.pythonhosted.org/packages/f1/6b/e4b0f8708f22dd7c321f87eadbb98708975e115ac6582eb46d1f32197ce6/contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1", size = 301672 }, + { url = "https://files.pythonhosted.org/packages/c3/87/201410522a756e605069078833d806147cad8532fdc164a96689d05c5afc/contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d", size = 820145 }, + { url = "https://files.pythonhosted.org/packages/b4/d9/42680a17d43edda04ab2b3f11125cf97b61bce5d3b52721a42960bf748bd/contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431", size = 399542 }, + { url = "https://files.pythonhosted.org/packages/55/14/0dc1884e3c04f9b073a47283f5d424926644250891db392a07c56f05e5c5/contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb", size = 477974 }, + { url = "https://files.pythonhosted.org/packages/8b/4f/be28a39cd5e988b8d3c2cc642c2c7ffeeb28fe80a86df71b6d1e473c5038/contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2", size = 248613 }, + { url = "https://files.pythonhosted.org/packages/2c/8e/656f8e7cd316aa68d9824744773e90dbd71f847429d10c82001e927480a2/contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b", size = 233603 }, + { url = "https://files.pythonhosted.org/packages/60/2a/4d4bd4541212ab98f3411f21bf58b0b246f333ae996e9f57e1acf12bcc45/contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b", size = 287037 }, + { url = "https://files.pythonhosted.org/packages/24/67/8abf919443381585a4eee74069e311c736350549dae02d3d014fef93d50a/contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532", size = 323274 }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6da11329dd35a2f2e404a95e5374b5702de6ac52e776e8b87dd6ea4b29d0/contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e", size = 297801 }, + { url = "https://files.pythonhosted.org/packages/b7/f6/78f60fa0b6ae64971178e2542e8b3ad3ba5f4f379b918ab7b18038a3f897/contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5", size = 302821 }, + { url = "https://files.pythonhosted.org/packages/da/25/6062395a1c6a06f46a577da821318886b8b939453a098b9cd61671bb497b/contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62", size = 820121 }, + { url = "https://files.pythonhosted.org/packages/41/5e/64e78b1e8682cbab10c13fc1a2c070d30acedb805ab2f42afbd3d88f7225/contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33", size = 401590 }, + { url = "https://files.pythonhosted.org/packages/e5/76/94bc17eb868f8c7397f8fdfdeae7661c1b9a35f3a7219da308596e8c252a/contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45", size = 480534 }, + { url = "https://files.pythonhosted.org/packages/94/0f/07a5e26fec7176658f6aecffc615900ff1d303baa2b67bc37fd98ce67c87/contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a", size = 249799 }, + { url = "https://files.pythonhosted.org/packages/32/0b/d7baca3f60d3b3a77c9ba1307c7792befd3c1c775a26c649dca1bfa9b6ba/contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e", size = 232739 }, + { url = "https://files.pythonhosted.org/packages/6d/62/a385b4d4b5718e3a933de5791528f45f1f5b364d3c79172ad0309c832041/contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442", size = 282171 }, + { url = "https://files.pythonhosted.org/packages/91/21/8c6819747fea53557f3963ca936035b3e8bed87d591f5278ad62516a059d/contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8", size = 321182 }, + { url = "https://files.pythonhosted.org/packages/22/29/d75da9002f9df09c755b12cf0357eb91b081c858e604f4e92b4b8bfc3c15/contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7", size = 295869 }, + { url = "https://files.pythonhosted.org/packages/a7/47/4e7e66159f881c131e3b97d1cc5c0ea72be62bdd292c7f63fd13937d07f4/contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf", size = 298756 }, + { url = "https://files.pythonhosted.org/packages/d3/bb/bffc99bc3172942b5eda8027ca0cb80ddd336fcdd634d68adce957d37231/contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d", size = 818441 }, + { url = "https://files.pythonhosted.org/packages/da/1b/904baf0aaaf6c6e2247801dcd1ff0d7bf84352839927d356b28ae804cbb0/contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6", size = 410294 }, + { url = "https://files.pythonhosted.org/packages/75/d4/c3b7a9a0d1f99b528e5a46266b0b9f13aad5a0dd1156d071418df314c427/contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970", size = 486678 }, + { url = "https://files.pythonhosted.org/packages/02/7e/ffaba1bf3719088be3ad6983a5e85e1fc9edccd7b406b98e433436ecef74/contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d", size = 247023 }, + { url = "https://files.pythonhosted.org/packages/a6/82/29f5ff4ae074c3230e266bc9efef449ebde43721a727b989dd8ef8f97d73/contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9", size = 232380 }, + { url = "https://files.pythonhosted.org/packages/9b/cb/08f884c4c2efd433a38876b1b8069bfecef3f2d21ff0ce635d455962f70f/contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217", size = 285830 }, + { url = "https://files.pythonhosted.org/packages/8e/57/cd4d4c99d999a25e9d518f628b4793e64b1ecb8ad3147f8469d8d4a80678/contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684", size = 322038 }, + { url = "https://files.pythonhosted.org/packages/32/b6/c57ed305a6f86731107fc183e97c7e6a6005d145f5c5228a44718082ad12/contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce", size = 295797 }, + { url = "https://files.pythonhosted.org/packages/8e/71/7f20855592cc929bc206810432b991ec4c702dc26b0567b132e52c85536f/contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8", size = 301124 }, + { url = "https://files.pythonhosted.org/packages/86/6d/52c2fc80f433e7cdc8624d82e1422ad83ad461463cf16a1953bbc7d10eb1/contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251", size = 819787 }, + { url = "https://files.pythonhosted.org/packages/d0/b0/f8d4548e89f929d6c5ca329df9afad6190af60079ec77d8c31eb48cf6f82/contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7", size = 400031 }, + { url = "https://files.pythonhosted.org/packages/96/1b/b05cd42c8d21767a0488b883b38658fb9a45f86c293b7b42521a8113dc5d/contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9", size = 477949 }, + { url = "https://files.pythonhosted.org/packages/16/d9/8a15ff67fc27c65939e454512955e1b240ec75cd201d82e115b3b63ef76d/contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba", size = 247396 }, + { url = "https://files.pythonhosted.org/packages/09/fe/086e6847ee53da10ddf0b6c5e5f877ab43e68e355d2f4c85f67561ee8a57/contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34", size = 232598 }, + { url = "https://files.pythonhosted.org/packages/a3/9c/662925239e1185c6cf1da8c334e4c61bddcfa8e528f4b51083b613003170/contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887", size = 286436 }, + { url = "https://files.pythonhosted.org/packages/d3/7e/417cdf65da7140981079eda6a81ecd593ae0239bf8c738f2e2b3f6df8920/contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718", size = 322629 }, + { url = "https://files.pythonhosted.org/packages/a8/22/ffd88aef74cc045698c5e5c400e8b7cd62311199c109245ac7827290df2c/contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f", size = 297117 }, + { url = "https://files.pythonhosted.org/packages/2b/c0/24c34c41a180f875419b536125799c61e2330b997d77a5a818a3bc3e08cd/contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85", size = 301855 }, + { url = "https://files.pythonhosted.org/packages/bf/ec/f9877f6378a580cd683bd76c8a781dcd972e82965e0da951a739d3364677/contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e", size = 820597 }, + { url = "https://files.pythonhosted.org/packages/e1/3a/c41f4bc7122d3a06388acae1bed6f50a665c1031863ca42bd701094dcb1f/contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0", size = 400031 }, + { url = "https://files.pythonhosted.org/packages/87/2b/9b49451f7412cc1a79198e94a771a4e52d65c479aae610b1161c0290ef2c/contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887", size = 435965 }, + { url = "https://files.pythonhosted.org/packages/e6/3c/fc36884b6793e2066a6ff25c86e21b8bd62553456b07e964c260bcf22711/contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e", size = 246493 }, + { url = "https://files.pythonhosted.org/packages/3d/85/f4c5b09ce79828ed4553a8ae2ebdf937794f57b45848b1f5c95d9744ecc2/contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3", size = 289240 }, + { url = "https://files.pythonhosted.org/packages/18/d3/9d7c0a372baf5130c1417a4b8275079d5379c11355436cb9fc78af7d7559/contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23", size = 476043 }, + { url = "https://files.pythonhosted.org/packages/e7/12/643242c3d9b031ca19f9a440f63e568dd883a04711056ca5d607f9bda888/contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb", size = 246247 }, + { url = "https://files.pythonhosted.org/packages/e1/37/95716fe235bf441422059e4afcd4b9b7c5821851c2aee992a06d1e9f831a/contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163", size = 289029 }, + { url = "https://files.pythonhosted.org/packages/e5/fd/14852c4a688031e0d8a20d9a1b60078d45507186ef17042093835be2f01a/contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c", size = 476043 }, +] + +[[package]] +name = "contourpy" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145 }, + { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681 }, + { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643 }, + { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/f1/17/c52d2970784383cafb0bd918b6fb036d98d96bbf0bc1befb5d1e31a07a70/contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5", size = 171353 }, + { url = "https://files.pythonhosted.org/packages/53/23/db9f69676308e094d3c45f20cc52e12d10d64f027541c995d89c11ad5c75/contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843", size = 211817 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/38/1e/94bda024d629f254143a134eead69e21c836429a2a6ce82209a00ddcb79a/contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb", size = 215838 }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", 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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534 }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196 }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489 }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535 }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550 }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791 }, +] + +[[package]] +name = "corelay" +source = { editable = "." } +dependencies = [ + { name = "click" }, + { name = "h5py", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "h5py", version = "3.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "matplotlib", version = "3.7.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "matplotlib", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "metrohash-python" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "scikit-image", version = "0.21.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "scikit-image", version = "0.24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "scipy", version = "1.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "coverage" }, + { name = "flake8" }, + { name = "pylint" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "setuptools" }, + { name = "sphinx" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-bibtex" }, + { name = "sphinxcontrib-datatemplates" }, + { name = "tox" }, + { name = "tox-uv", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "tox-uv", version = "1.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=7.0,<8.0.0" }, + { name = "h5py", specifier = ">=3.0.0,<4.0.0" }, + { name = "matplotlib", specifier = ">=3.0.3,<4.0.0" }, + { name = "metrohash-python", specifier = ">=1.1.3.post2,<2.0.0" }, + { name = "numpy", specifier = ">=1.16.3,<2.0.0" }, + { name = "scikit-image", specifier = ">=0.18.0,<1.0.0" }, + { name = "scikit-learn", specifier = ">=0.20.3,<1.0.0" }, + { name = "scipy", specifier = ">=1.2.1,<2.0.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "coverage", specifier = ">=5.5.0,<6.0.0" }, + { name = "flake8", specifier = ">=3.9.2,<4.0.0" }, + { name = "pylint", specifier = ">=2.17.7,<3.0.0" }, + { name = "pytest", specifier = ">=6.2.5,<7.0.0" }, + { name = "pytest-cov", specifier = ">=2.12.1,<3.0.0" }, + { name = "setuptools", specifier = ">=58.1.0,<59.0.0" }, + { name = "sphinx", specifier = ">=4.2.0,<5.0.0" }, + { name = "sphinx-copybutton", specifier = ">=0.4.0,<1.0.0" }, + { name = "sphinx-rtd-theme", specifier = ">=1.0.0,<2.0.0" }, + { name = "sphinxcontrib-bibtex", specifier = ">=2.4.1,<3.0.0" }, + { name = "sphinxcontrib-datatemplates", specifier = ">=0.9.0,<1.0.0" }, + { name = "tox", specifier = ">=4.12.1,<5.0.0" }, + { name = "tox-uv", specifier = ">=1.0.0,<2.0.0" }, +] + +[[package]] +name = "coverage" +version = "5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/df/d5e67851e83948def768d7fb1a0fd373665b20f56ff63ed220c6cd16cb11/coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", size = 691258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/a2/43dd30964103a7ff1fd03392a30a5b08105bc85d1bafbfc51023a1bb4fd3/coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", size = 207511 }, + { url = "https://files.pythonhosted.org/packages/d4/3e/4f6451b8b09a1eb2d0e7f61a3d7019bd98d556fc5343378f76e8905b2789/coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", size = 238991 }, + { url = "https://files.pythonhosted.org/packages/a2/a2/10974646b530b043a04cfe3ffa38a0f3b31a04c3b19cc68b9627b2c8e8dc/coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", size = 211489 }, + { url = "https://files.pythonhosted.org/packages/b6/26/b53bf0fef1b4bce6f7d61fef10fbf924d943987d4c9e53c394ecebff3673/coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", size = 207633 }, + { url = "https://files.pythonhosted.org/packages/62/05/9f43545401929bd0f49a2e0e4e38f69f6f7a59c1be88d1f63c34aebd7684/coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", size = 243719 }, + { url = "https://files.pythonhosted.org/packages/2b/7c/841e2d203dd1df07ceba0f844e5011e4341ce854207687d4c4d68ab8ff95/coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", size = 245320 }, + { url = "https://files.pythonhosted.org/packages/0a/eb/c6975d34252096a06883726f1898cd62da9b9ccafc2ed9a4f279fe45999e/coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", size = 243721 }, + { url = "https://files.pythonhosted.org/packages/a4/3a/8f7b217265503eae2b0ea97e714e2709e1e84ee13cd3ca6abdff1e99e76c/coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", size = 245325 }, + { url = "https://files.pythonhosted.org/packages/15/a4/5d5b5e2001d384b97cdaf3f19f52370a5abe9a5b1db876c580418bcb1151/coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", size = 210316 }, + { url = "https://files.pythonhosted.org/packages/e9/92/45878381df409a4b5e5a6399e522964cfd4e6976d07f6300e97eebd6e307/coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", size = 211496 }, + { url = "https://files.pythonhosted.org/packages/0d/8a/3b13c4e1f241a7083a4ee9986b969f0238f41dcd7a8990c786bc3b4b5b19/coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", size = 207640 }, + { url = "https://files.pythonhosted.org/packages/1d/7d/57fe5de17d64f1be36a75aeef15dda8fdd64a241612bd79071b9d79e9bed/coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", size = 242068 }, + { url = "https://files.pythonhosted.org/packages/21/77/0ae9b19cb99d0a41b740535d5f9502e9bc6b32a24eff6b0bcb182e2cce56/coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", size = 243533 }, + { url = "https://files.pythonhosted.org/packages/d3/f9/798152327a3fef063f66528493a74316501cff54e09bfdcd50723229ce15/coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", size = 242068 }, + { url = "https://files.pythonhosted.org/packages/a4/79/625f1ed5da2a69f52fb44e0b7ca1b470437ff502348c716005a98a71cd49/coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", size = 243537 }, + { url = "https://files.pythonhosted.org/packages/76/7b/81dafcea3845e8bf1baf126dacbb2c079d7ce30072fa1514262539911b5e/coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", size = 210316 }, + { url = "https://files.pythonhosted.org/packages/60/41/fa43e0321f0b6157eb017c6c098604a2fe02b553e4ea5aebe51f5dbbd612/coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", size = 211503 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "docutils" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/17/559b4d020f4b46e0287a2eddf2d8ebf76318fd3bd495f1625414b052fdc9/docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125", size = 2016138 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5e/6003a0d1f37725ec2ebd4046b657abb9372202655f96e76795dca8c0063c/docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61", size = 575533 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "flake8" +version = "3.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/47/15b267dfe7e03dca4c4c06e7eadbd55ef4dfd368b13a0bab36d708b14366/flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", size = 164777 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/80/35a0716e5d5101e643404dabd20f07f5528a21f3ef4032d31a49c913237b/flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907", size = 73147 }, +] + +[[package]] +name = "fonttools" +version = "4.57.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/17/3ddfd1881878b3f856065130bb603f5922e81ae8a4eb53bce0ea78f765a8/fonttools-4.57.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:babe8d1eb059a53e560e7bf29f8e8f4accc8b6cfb9b5fd10e485bde77e71ef41", size = 2756260 }, + { url = "https://files.pythonhosted.org/packages/26/2b/6957890c52c030b0bf9e0add53e5badab4682c6ff024fac9a332bb2ae063/fonttools-4.57.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81aa97669cd726349eb7bd43ca540cf418b279ee3caba5e2e295fb4e8f841c02", size = 2284691 }, + { url = "https://files.pythonhosted.org/packages/cc/8e/c043b4081774e5eb06a834cedfdb7d432b4935bc8c4acf27207bdc34dfc4/fonttools-4.57.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0e9618630edd1910ad4f07f60d77c184b2f572c8ee43305ea3265675cbbfe7e", size = 4566077 }, + { url = "https://files.pythonhosted.org/packages/59/bc/e16ae5d9eee6c70830ce11d1e0b23d6018ddfeb28025fda092cae7889c8b/fonttools-4.57.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34687a5d21f1d688d7d8d416cb4c5b9c87fca8a1797ec0d74b9fdebfa55c09ab", size = 4608729 }, + { url = "https://files.pythonhosted.org/packages/25/13/e557bf10bb38e4e4c436d3a9627aadf691bc7392ae460910447fda5fad2b/fonttools-4.57.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69ab81b66ebaa8d430ba56c7a5f9abe0183afefd3a2d6e483060343398b13fb1", size = 4759646 }, + { url = "https://files.pythonhosted.org/packages/bc/c9/5e2952214d4a8e31026bf80beb18187199b7001e60e99a6ce19773249124/fonttools-4.57.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d639397de852f2ccfb3134b152c741406752640a266d9c1365b0f23d7b88077f", size = 4941652 }, + { url = "https://files.pythonhosted.org/packages/df/04/e80242b3d9ec91a1f785d949edc277a13ecfdcfae744de4b170df9ed77d8/fonttools-4.57.0-cp310-cp310-win32.whl", hash = "sha256:cc066cb98b912f525ae901a24cd381a656f024f76203bc85f78fcc9e66ae5aec", size = 2159432 }, + { url = "https://files.pythonhosted.org/packages/33/ba/e858cdca275daf16e03c0362aa43734ea71104c3b356b2100b98543dba1b/fonttools-4.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:7a64edd3ff6a7f711a15bd70b4458611fb240176ec11ad8845ccbab4fe6745db", size = 2203869 }, + { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392 }, + { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609 }, + { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292 }, + { url = "https://files.pythonhosted.org/packages/41/b8/d5933559303a4ab18c799105f4c91ee0318cc95db4a2a09e300116625e7a/fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746", size = 4902503 }, + { url = "https://files.pythonhosted.org/packages/32/13/acb36bfaa316f481153ce78de1fa3926a8bad42162caa3b049e1afe2408b/fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344", size = 5077351 }, + { url = "https://files.pythonhosted.org/packages/b5/23/6d383a2ca83b7516d73975d8cca9d81a01acdcaa5e4db8579e4f3de78518/fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f", size = 5275067 }, + { url = "https://files.pythonhosted.org/packages/bc/ca/31b8919c6da0198d5d522f1d26c980201378c087bdd733a359a1e7485769/fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36", size = 2158263 }, + { url = "https://files.pythonhosted.org/packages/13/4c/de2612ea2216eb45cfc8eb91a8501615dd87716feaf5f8fb65cbca576289/fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d", size = 2204968 }, + { url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824 }, + { url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072 }, + { url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020 }, + { url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096 }, + { url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356 }, + { url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546 }, + { url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776 }, + { url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956 }, + { url = "https://files.pythonhosted.org/packages/e9/2f/11439f3af51e4bb75ac9598c29f8601aa501902dcedf034bdc41f47dd799/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef", size = 2739175 }, + { url = "https://files.pythonhosted.org/packages/25/52/677b55a4c0972dc3820c8dba20a29c358197a78229daa2ea219fdb19e5d5/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c", size = 2276583 }, + { url = "https://files.pythonhosted.org/packages/64/79/184555f8fa77b827b9460a4acdbbc0b5952bb6915332b84c615c3a236826/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72", size = 4766437 }, + { url = "https://files.pythonhosted.org/packages/f8/ad/c25116352f456c0d1287545a7aa24e98987b6d99c5b0456c4bd14321f20f/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817", size = 4838431 }, + { url = "https://files.pythonhosted.org/packages/53/ae/398b2a833897297797a44f519c9af911c2136eb7aa27d3f1352c6d1129fa/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9", size = 4951011 }, + { url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679 }, + { url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833 }, + { url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799 }, + { url = "https://files.pythonhosted.org/packages/8a/3f/c16dbbec7221783f37dcc2022d5a55f0d704ffc9feef67930f6eb517e8ce/fonttools-4.57.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d57b4e23ebbe985125d3f0cabbf286efa191ab60bbadb9326091050d88e8213", size = 2753756 }, + { url = "https://files.pythonhosted.org/packages/48/9f/5b4a3d6aed5430b159dd3494bb992d4e45102affa3725f208e4f0aedc6a3/fonttools-4.57.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:579ba873d7f2a96f78b2e11028f7472146ae181cae0e4d814a37a09e93d5c5cc", size = 2283179 }, + { url = "https://files.pythonhosted.org/packages/17/b2/4e887b674938b4c3848029a4134ac90dd8653ea80b4f464fa1edeae37f25/fonttools-4.57.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3e1ec10c29bae0ea826b61f265ec5c858c5ba2ce2e69a71a62f285cf8e4595", size = 4647139 }, + { url = "https://files.pythonhosted.org/packages/a5/0e/b6314a09a4d561aaa7e09de43fa700917be91e701f07df6178865962666c/fonttools-4.57.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1968f2a2003c97c4ce6308dc2498d5fd4364ad309900930aa5a503c9851aec8", size = 4691211 }, + { url = "https://files.pythonhosted.org/packages/bf/1d/b9f4b70d165c25f5c9aee61eb6ae90b0e9b5787b2c0a45e4f3e50a839274/fonttools-4.57.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:aff40f8ac6763d05c2c8f6d240c6dac4bb92640a86d9b0c3f3fff4404f34095c", size = 4873755 }, + { url = "https://files.pythonhosted.org/packages/3b/fa/a731c8f42ae2c6761d1c22bd3c90241d5b2b13cabb70598abc74a828b51f/fonttools-4.57.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d07f1b64008e39fceae7aa99e38df8385d7d24a474a8c9872645c4397b674481", size = 5070072 }, + { url = "https://files.pythonhosted.org/packages/1f/1e/6a988230109a2ba472e5de0a4c3936d49718cfc4b700b6bad53eca414bcf/fonttools-4.57.0-cp38-cp38-win32.whl", hash = "sha256:51d8482e96b28fb28aa8e50b5706f3cee06de85cbe2dce80dbd1917ae22ec5a6", size = 1484098 }, + { url = "https://files.pythonhosted.org/packages/dc/7a/2b3666e8c13d035adf656a8ae391380656144760353c97f74747c64fd3e5/fonttools-4.57.0-cp38-cp38-win_amd64.whl", hash = "sha256:03290e818782e7edb159474144fca11e36a8ed6663d1fcbd5268eb550594fd8e", size = 1529536 }, + { url = "https://files.pythonhosted.org/packages/d2/c7/3bddafbb95447f6fbabdd0b399bf468649321fd4029e356b4f6bd70fbc1b/fonttools-4.57.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7339e6a3283e4b0ade99cade51e97cde3d54cd6d1c3744459e886b66d630c8b3", size = 2758942 }, + { url = "https://files.pythonhosted.org/packages/d4/a2/8dd7771022e365c90e428b1607174c3297d5c0a2cc2cf4cdccb2221945b7/fonttools-4.57.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:05efceb2cb5f6ec92a4180fcb7a64aa8d3385fd49cfbbe459350229d1974f0b1", size = 2285959 }, + { url = "https://files.pythonhosted.org/packages/58/5a/2fd29c5e38b14afe1fae7d472373e66688e7c7a98554252f3cf44371e033/fonttools-4.57.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a97bb05eb24637714a04dee85bdf0ad1941df64fe3b802ee4ac1c284a5f97b7c", size = 4571677 }, + { url = "https://files.pythonhosted.org/packages/bf/30/b77cf81923f1a67ff35d6765a9db4718c0688eb8466c464c96a23a2e28d4/fonttools-4.57.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:541cb48191a19ceb1a2a4b90c1fcebd22a1ff7491010d3cf840dd3a68aebd654", size = 4616644 }, + { url = "https://files.pythonhosted.org/packages/06/33/376605898d8d553134144dff167506a49694cb0e0cf684c14920fbc1e99f/fonttools-4.57.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cdef9a056c222d0479a1fdb721430f9efd68268014c54e8166133d2643cb05d9", size = 4761314 }, + { url = "https://files.pythonhosted.org/packages/48/e4/e0e48f5bae04bc1a1c6b4fcd7d1ca12b29f1fe74221534b7ff83ed0db8fe/fonttools-4.57.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3cf97236b192a50a4bf200dc5ba405aa78d4f537a2c6e4c624bb60466d5b03bd", size = 4945563 }, + { url = "https://files.pythonhosted.org/packages/61/98/2dacfc6d70f2d93bde1bbf814286be343cb17f53057130ad3b843144dd00/fonttools-4.57.0-cp39-cp39-win32.whl", hash = "sha256:e952c684274a7714b3160f57ec1d78309f955c6335c04433f07d36c5eb27b1f9", size = 2159997 }, + { url = "https://files.pythonhosted.org/packages/93/fa/e61cc236f40d504532d2becf90c297bfed8e40abc0c8b08375fbb83eff29/fonttools-4.57.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2a722c0e4bfd9966a11ff55c895c817158fcce1b2b6700205a376403b546ad9", size = 2204508 }, + { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605 }, +] + +[[package]] +name = "h5py" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/8f/e557819155a282da36fb21f8de4730cfd10a964b52b3ae8d20157ac1c668/h5py-3.11.0.tar.gz", hash = "sha256:7b7e8f78072a2edec87c9836f25f34203fd492a4475709a18b417a33cfb21fa9", size = 406519 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/25/a1cc81b3a742b73f9409bafe4762c9de0940cce0955d4b6754698fd5ce44/h5py-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1625fd24ad6cfc9c1ccd44a66dac2396e7ee74940776792772819fc69f3a3731", size = 3477113 }, + { url = "https://files.pythonhosted.org/packages/d4/03/bbb9a992fb43d3ce46687b7c14107f0fa56e6c8704c9ca945a9392cbc8ce/h5py-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c072655ad1d5fe9ef462445d3e77a8166cbfa5e599045f8aa3c19b75315f10e5", size = 2939879 }, + { url = "https://files.pythonhosted.org/packages/94/00/94bf8573e7487b7c37f2b613fc381880d48ec2311f2e859b8a5817deb4df/h5py-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77b19a40788e3e362b54af4dcf9e6fde59ca016db2c61360aa30b47c7b7cef00", size = 5306122 }, + { url = "https://files.pythonhosted.org/packages/bb/0d/fbadb9c69e2a31f641bc24e8d21671129ef3b73f0c61bb16b094fadf1385/h5py-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef4e2f338fc763f50a8113890f455e1a70acd42a4d083370ceb80c463d803972", size = 2968816 }, + { url = "https://files.pythonhosted.org/packages/a0/52/38bb74cc4362738cc7ef819503fc54d70f0c3a7378519ccb0ac309389122/h5py-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbd732a08187a9e2a6ecf9e8af713f1d68256ee0f7c8b652a32795670fb481ba", size = 3489913 }, + { url = "https://files.pythonhosted.org/packages/f0/af/dfbea0c69fe725e9e77259d42f4e14eb582eb094200aaf697feb36f513d8/h5py-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75bd7b3d93fbeee40860fd70cdc88df4464e06b70a5ad9ce1446f5f32eb84007", size = 2946912 }, + { url = "https://files.pythonhosted.org/packages/af/26/f231ee425c8df93c1abbead3d90ea4a5ff3d6aa49e0edfd3b4c017e74844/h5py-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c416f8eb0daae39dabe71415cb531f95dce2d81e1f61a74537a50c63b28ab3", size = 5420165 }, + { url = "https://files.pythonhosted.org/packages/d8/5e/b7b83cfe60504cc4d24746aed04353af7ea8ec104e597e5ae71b8d0390cb/h5py-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:083e0329ae534a264940d6513f47f5ada617da536d8dccbafc3026aefc33c90e", size = 2979079 }, + { url = "https://files.pythonhosted.org/packages/58/a9/2655d4b8355d0ee783dc89dd40b5f0780e6f54a4c9b60721dc235fd6c457/h5py-3.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a76cae64080210389a571c7d13c94a1a6cf8cb75153044fd1f822a962c97aeab", size = 3466468 }, + { url = "https://files.pythonhosted.org/packages/9d/3f/cf80ef55e0a9b18aae96c763fbd275c54d0723e0f2cc54f954f87cc5c69a/h5py-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3736fe21da2b7d8a13fe8fe415f1272d2a1ccdeff4849c1421d2fb30fd533bc", size = 2943214 }, + { url = "https://files.pythonhosted.org/packages/db/7e/fedac8bb8c4729409e2dec5e4136a289116d701d54f69ce73c5617afc5f0/h5py-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6ae84a14103e8dc19266ef4c3e5d7c00b68f21d07f2966f0ca7bdb6c2761fb", size = 5378375 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/0ee327933ffa37af1fc7915df7fc067e6009adcd8445d55ad07a9bec11b5/h5py-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:21dbdc5343f53b2e25404673c4f00a3335aef25521bd5fa8c707ec3833934892", size = 2970991 }, + { url = "https://files.pythonhosted.org/packages/33/97/c1a8f28329ad794d18fc61bf251268ac03959bf93b82fdd7701ac6931fed/h5py-3.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:754c0c2e373d13d6309f408325343b642eb0f40f1a6ad21779cfa9502209e150", size = 3470228 }, + { url = "https://files.pythonhosted.org/packages/a4/1d/fd0b88c51c37bc8aeedecc4f4b48397f7ce13c87073aaf6912faec06e9f6/h5py-3.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:731839240c59ba219d4cb3bc5880d438248533366f102402cfa0621b71796b62", size = 2935809 }, + { url = "https://files.pythonhosted.org/packages/86/43/fd0bd74462b3c3fb35d98568935d3e5a435c8ec24d45ef408ac8869166af/h5py-3.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ec9df3dd2018904c4cc06331951e274f3f3fd091e6d6cc350aaa90fa9b42a76", size = 5309045 }, + { url = "https://files.pythonhosted.org/packages/15/9a/b5456e1acc4abb382938d4a730600823bfe77a4bbfd29140ccbf01ba5596/h5py-3.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:55106b04e2c83dfb73dc8732e9abad69d83a436b5b82b773481d95d17b9685e1", size = 2989172 }, + { url = "https://files.pythonhosted.org/packages/c2/1f/36a84945616881bd47e6c40dcdca7e929bc811725d78d001eddba6864185/h5py-3.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f4e025e852754ca833401777c25888acb96889ee2c27e7e629a19aee288833f0", size = 3490090 }, + { url = "https://files.pythonhosted.org/packages/3c/fb/e213586de5ea56f1747a843e725c62eef350512be57452186996ba660d52/h5py-3.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c4b760082626120031d7902cd983d8c1f424cdba2809f1067511ef283629d4b", size = 2951710 }, + { url = "https://files.pythonhosted.org/packages/71/28/69a881e01f198ccdb65c36f7adcfef22bfe85e38ffbfdf833af24f58eb5e/h5py-3.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67462d0669f8f5459529de179f7771bd697389fcb3faab54d63bf788599a48ea", size = 5326481 }, + { url = "https://files.pythonhosted.org/packages/c3/61/0b35ad9aac0ab0a33365879556fdb824fc83013df69b247386690db59015/h5py-3.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:d9c944d364688f827dc889cf83f1fca311caf4fa50b19f009d1f2b525edd33a3", size = 2978689 }, +] + +[[package]] +name = "h5py" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/8a/bc76588ff1a254e939ce48f30655a8f79fac614ca8bd1eda1a79fa276671/h5py-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5540daee2b236d9569c950b417f13fd112d51d78b4c43012de05774908dff3f5", size = 3413286 }, + { url = "https://files.pythonhosted.org/packages/19/bd/9f249ecc6c517b2796330b0aab7d2351a108fdbd00d4bb847c0877b5533e/h5py-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:10894c55d46df502d82a7a4ed38f9c3fdbcb93efb42e25d275193e093071fade", size = 2915673 }, + { url = "https://files.pythonhosted.org/packages/72/71/0dd079208d7d3c3988cebc0776c2de58b4d51d8eeb6eab871330133dfee6/h5py-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb267ce4b83f9c42560e9ff4d30f60f7ae492eacf9c7ede849edf8c1b860e16b", size = 4283822 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/0b6a59a1043c53d5d287effa02303bd248905ee82b25143c7caad8b340ad/h5py-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2cf6a231a07c14acd504a945a6e9ec115e0007f675bde5e0de30a4dc8d86a31", size = 4548100 }, + { url = "https://files.pythonhosted.org/packages/12/42/ad555a7ff7836c943fe97009405566dc77bcd2a17816227c10bd067a3ee1/h5py-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:851ae3a8563d87a5a0dc49c2e2529c75b8842582ccaefbf84297d2cfceeacd61", size = 2950547 }, + { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922 }, + { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619 }, + { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366 }, + { url = "https://files.pythonhosted.org/packages/03/71/c99f662d4832c8835453cf3476f95daa28372023bda4aa1fca9e97c24f09/h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782ff0ac39f455f21fd1c8ebc007328f65f43d56718a89327eec76677ebf238a", size = 4509058 }, + { url = "https://files.pythonhosted.org/packages/56/89/e3ff23e07131ff73a72a349be9639e4de84e163af89c1c218b939459a98a/h5py-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:22ffe2a25770a2d67213a1b94f58006c14dce06933a42d2aaa0318c5868d1508", size = 2966428 }, + { url = "https://files.pythonhosted.org/packages/d8/20/438f6366ba4ded80eadb38f8927f5e2cd6d2e087179552f20ae3dbcd5d5b/h5py-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:477c58307b6b9a2509c59c57811afb9f598aedede24a67da808262dfa0ee37b4", size = 3384442 }, + { url = "https://files.pythonhosted.org/packages/10/13/cc1cb7231399617d9951233eb12fddd396ff5d4f7f057ee5d2b1ca0ee7e7/h5py-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57c4c74f627c616f02b7aec608a8c706fe08cb5b0ba7c08555a4eb1dde20805a", size = 2917567 }, + { url = "https://files.pythonhosted.org/packages/9e/d9/aed99e1c858dc698489f916eeb7c07513bc864885d28ab3689d572ba0ea0/h5py-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:357e6dc20b101a805ccfd0024731fbaf6e8718c18c09baf3b5e4e9d198d13fca", size = 4669544 }, + { url = "https://files.pythonhosted.org/packages/a7/da/3c137006ff5f0433f0fb076b1ebe4a7bf7b5ee1e8811b5486af98b500dd5/h5py-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6f13f9b5ce549448c01e4dfe08ea8d1772e6078799af2c1c8d09e941230a90d", size = 4932139 }, + { url = "https://files.pythonhosted.org/packages/25/61/d897952629cae131c19d4c41b2521e7dd6382f2d7177c87615c2e6dced1a/h5py-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:21daf38171753899b5905f3d82c99b0b1ec2cbbe282a037cad431feb620e62ec", size = 2954179 }, + { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040 }, + { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766 }, + { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255 }, + { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580 }, + { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890 }, + { url = "https://files.pythonhosted.org/packages/cd/91/3e5b4e4c399bb57141a2451c67808597ab6993f799587566c9f11dbaefe9/h5py-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82690e89c72b85addf4fc4d5058fb1e387b6c14eb063b0b879bf3f42c3b93c35", size = 3424729 }, + { url = "https://files.pythonhosted.org/packages/12/82/4e455e12e7ff26533c762eaf324edd6b076f84c3a003a40a1e52d805e0fb/h5py-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d571644958c5e19a61c793d8d23cd02479572da828e333498c9acc463f4a3997", size = 2926632 }, + { url = "https://files.pythonhosted.org/packages/ab/c9/fb430d3277e81eade92e54e87bd73e9f60c98240a86a5f43e3b85620d7d8/h5py-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:560e71220dc92dfa254b10a4dcb12d56b574d2d87e095db20466b32a93fec3f9", size = 4285580 }, + { url = "https://files.pythonhosted.org/packages/3f/9b/3e8cded7877ec84b707df82b9c6289cd1d7ad80fef9a10bb1389c5fee8f2/h5py-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10f061764d8dce0a9592ce08bfd5f243a00703325c388f1086037e5d619c5f1", size = 4550898 }, + { url = "https://files.pythonhosted.org/packages/cb/47/8353102cff9290861135e13eefff5a916855d2ab23bd052ec7ac144f4c48/h5py-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c82ece71ed1c2b807b6628e3933bc6eae57ea21dac207dca3470e3ceaaf437c", size = 2960208 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imageio" +version = "2.35.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bf/d0ddda79819405428f40e4bc9245c2b936a3a2b23d83b6e42d83822ef822/imageio-2.35.1.tar.gz", hash = "sha256:4952dfeef3c3947957f6d5dedb1f4ca31c6e509a476891062396834048aeed2a", size = 389686 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/b7/02adac4e42a691008b5cfb31db98c190e1fc348d1521b9be4429f9454ed1/imageio-2.35.1-py3-none-any.whl", hash = "sha256:6eb2e5244e7a16b85c10b5c2fe0f7bf961b40fcb9f1a9fd1bd1d2c2f8fb3cd65", size = 315378 }, +] + +[[package]] +name = "imageio" +version = "2.37.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "zipp", version = "3.21.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, +] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/be/f3e8c6081b684f176b761e6a2fef02a0be939740ed6f54109a2951d806f3/importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", size = 43372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "zipp", version = "3.21.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "markupsafe", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "joblib" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644 }, + { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877 }, + { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645 }, + { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022 }, + { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367 }, + { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884 }, + { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 }, + { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 }, + { url = "https://files.pythonhosted.org/packages/57/d6/620247574d9e26fe24384087879e8399e309f0051782f95238090afa6ccc/kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a", size = 122325 }, + { url = "https://files.pythonhosted.org/packages/bd/c6/572ad7d73dbd898cffa9050ffd7ff7e78a055a1d9b7accd6b4d1f50ec858/kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade", size = 65679 }, + { url = "https://files.pythonhosted.org/packages/14/a7/bb8ab10e12cc8764f4da0245d72dee4731cc720bdec0f085d5e9c6005b98/kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c", size = 64267 }, + { url = "https://files.pythonhosted.org/packages/54/a4/3b5a2542429e182a4df0528214e76803f79d016110f5e67c414a0357cd7d/kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95", size = 1387236 }, + { url = "https://files.pythonhosted.org/packages/a6/d7/bc3005e906c1673953a3e31ee4f828157d5e07a62778d835dd937d624ea0/kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b", size = 1500555 }, + { url = "https://files.pythonhosted.org/packages/09/a7/87cb30741f13b7af08446795dca6003491755805edc9c321fe996c1320b8/kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3", size = 1431684 }, + { url = "https://files.pythonhosted.org/packages/37/a4/1e4e2d8cdaa42c73d523413498445247e615334e39401ae49dae74885429/kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503", size = 1125811 }, + { url = "https://files.pythonhosted.org/packages/76/36/ae40d7a3171e06f55ac77fe5536079e7be1d8be2a8210e08975c7f9b4d54/kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf", size = 1179987 }, + { url = "https://files.pythonhosted.org/packages/d8/5d/6e4894b9fdf836d8bd095729dff123bbbe6ad0346289287b45c800fae656/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933", size = 2186817 }, + { url = "https://files.pythonhosted.org/packages/f0/2d/603079b2c2fd62890be0b0ebfc8bb6dda8a5253ca0758885596565b0dfc1/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e", size = 2332538 }, + { url = "https://files.pythonhosted.org/packages/bb/2a/9a28279c865c38a27960db38b07179143aafc94877945c209bfc553d9dd3/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89", size = 2293890 }, + { url = "https://files.pythonhosted.org/packages/1a/4d/4da8967f3bf13c764984b8fbae330683ee5fbd555b4a5624ad2b9decc0ab/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d", size = 2434677 }, + { url = "https://files.pythonhosted.org/packages/08/e9/a97a2b6b74dd850fa5974309367e025c06093a143befe9b962d0baebb4f0/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5", size = 2250339 }, + { url = "https://files.pythonhosted.org/packages/8a/e7/55507a387ba1766e69f5e13a79e1aefabdafe0532bee5d1972dfc42b3d16/kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a", size = 46932 }, + { url = "https://files.pythonhosted.org/packages/52/77/7e04cca2ff1dc6ee6b7654cebe233de72b7a3ec5616501b6f3144fb70740/kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09", size = 55836 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/35/d3/5f2ecb94b5211c8a04f218a76133cc8d6d153b0f9cd0b45fad79907f0689/kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644", size = 46980 }, + { url = "https://files.pythonhosted.org/packages/ef/17/cd10d020578764ea91740204edc6b3236ed8106228a46f568d716b11feb2/kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4", size = 55847 }, + { url = "https://files.pythonhosted.org/packages/91/84/32232502020bd78d1d12be7afde15811c64a95ed1f606c10456db4e4c3ac/kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f", size = 48494 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715 }, + { url = "https://files.pythonhosted.org/packages/64/f3/2403d90821fffe496df16f6996cb328b90b0d80c06d2938a930a7732b4f1/kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00", size = 59662 }, + { url = "https://files.pythonhosted.org/packages/fa/7d/8f409736a4a6ac04354fa530ebf46682ddb1539b0bae15f4731ff2c575bc/kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935", size = 57753 }, + { url = "https://files.pythonhosted.org/packages/4c/a5/3937c9abe8eedb1356071739ad437a0b486cbad27d54f4ec4733d24882ac/kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b", size = 103564 }, + { url = "https://files.pythonhosted.org/packages/b2/18/a5ae23888f010b90d5eb8d196fed30e268056b2ded54d25b38a193bb70e9/kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d", size = 95264 }, + { url = "https://files.pythonhosted.org/packages/f9/d0/c4240ae86306d4395e9701f1d7e6ddcc6d60c28cb0127139176cfcfc9ebe/kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d", size = 78197 }, + { url = "https://files.pythonhosted.org/packages/62/db/62423f0ab66813376a35c1e7da488ebdb4e808fcb54b7cec33959717bda1/kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2", size = 56080 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623 }, + { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720 }, + { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413 }, + { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826 }, + { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231 }, + { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938 }, + { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799 }, + { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362 }, + { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695 }, + { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802 }, + { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646 }, + { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260 }, + { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633 }, + { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885 }, + { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175 }, + { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994 }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804 }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690 }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839 }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109 }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269 }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468 }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394 }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901 }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306 }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966 }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311 }, + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156 }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071 }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053 }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278 }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139 }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517 }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952 }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132 }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997 }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060 }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471 }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793 }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855 }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430 }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294 }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736 }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194 }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942 }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341 }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455 }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138 }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857 }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129 }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538 }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, + { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403 }, + { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657 }, + { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948 }, + { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186 }, + { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279 }, + { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762 }, +] + +[[package]] +name = "latexcodec" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/e7/ed339caf3662976949e4fdbfdf4a6db818b8d2aa1cf2b5f73af89e936bba/latexcodec-3.0.0.tar.gz", hash = "sha256:917dc5fe242762cc19d963e6548b42d63a118028cdd3361d62397e3b638b6bc5", size = 31023 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/bf/ea8887e9f31a8f93ca306699d11909c6140151393a4216f0d9f85a004077/latexcodec-3.0.0-py3-none-any.whl", hash = "sha256:6f3477ad5e61a0a99bd31a6a370c34e88733a6bad9c921a3ffcfacada12f41a7", size = 18150 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 }, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/f0/f02e2d150d581a294efded4020094a371bbab42423fe78625ac18854d89b/lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69", size = 43271 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/42/a96d9d153f6ea38b925494cb9b42cf4a9f98fd30cad3124fc22e9d04ec34/lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977", size = 27432 }, + { url = "https://files.pythonhosted.org/packages/4a/0d/b325461e43dde8d7644e9b9e9dd57f2a4af472b588c51ccbc92778e60ea4/lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3", size = 69133 }, + { url = "https://files.pythonhosted.org/packages/8b/fc/83711d743fb5aaca5747bbf225fe3b5cbe085c7f6c115856b5cce80f3224/lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05", size = 68272 }, + { url = "https://files.pythonhosted.org/packages/8d/b5/ea47215abd4da45791664d7bbfe2976ca0de2c37af38b5e9e6cf89e0e65e/lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895", size = 70891 }, + { url = "https://files.pythonhosted.org/packages/8b/9b/908e12e5fa265ea1579261ff80f7b2136fd2ba254bc7f4f7e3dba83fd0f2/lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83", size = 70451 }, + { url = "https://files.pythonhosted.org/packages/16/ab/d9a47f2e70767af5ee311d71109be6ef2991c66c77bfa18e66707edd9f8c/lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9", size = 25778 }, + { url = "https://files.pythonhosted.org/packages/74/d6/0104e4154d2c30227eb54491dda8a4132be046b4cb37fb4ce915a5abc0d5/lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4", size = 27551 }, + { url = "https://files.pythonhosted.org/packages/ff/e1/99a7ec68b892c9b8c6212617f54e7e9b0304d47edad8c0ff043ae3aeb1a9/lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c", size = 27434 }, + { url = "https://files.pythonhosted.org/packages/1a/76/6a41de4b44d1dcfe4c720d4606de0d7b69b6b450f0bdce16f2e1fb8abc89/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4", size = 70687 }, + { url = "https://files.pythonhosted.org/packages/1e/5d/eaa12126e8989c9bdd21d864cbba2b258cb9ee2f574ada1462a0004cfad8/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56", size = 69757 }, + { url = "https://files.pythonhosted.org/packages/53/a9/6f22cfe9572929656988b72c0de266c5d10755369b575322725f67364c4e/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9", size = 73709 }, + { url = "https://files.pythonhosted.org/packages/bd/e6/b10fd94710a99a6309f3ad61a4eb480944bbb17fcb41bd2d852fdbee57ee/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f", size = 73191 }, + { url = "https://files.pythonhosted.org/packages/c9/78/a9b9d314da02fe66b632f2354e20e40fc3508befb450b5a17987a222b383/lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03", size = 25773 }, + { url = "https://files.pythonhosted.org/packages/94/e6/e2d3b0c9efe61f72dc327ce2355941f540e0b0d1f2b3490cbab6bab7d3ea/lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6", size = 27550 }, + { url = "https://files.pythonhosted.org/packages/d0/5d/768a7f2ccebb29604def61842fd54f6f5f75c79e366ee8748dda84de0b13/lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba", size = 27560 }, + { url = "https://files.pythonhosted.org/packages/b3/ce/f369815549dbfa4bebed541fa4e1561d69e4f268a1f6f77da886df182dab/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43", size = 72403 }, + { url = "https://files.pythonhosted.org/packages/44/46/3771e0a4315044aa7b67da892b2fb1f59dfcf0eaff2c8967b2a0a85d5896/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9", size = 72401 }, + { url = "https://files.pythonhosted.org/packages/81/39/84ce4740718e1c700bd04d3457ac92b2e9ce76529911583e7a2bf4d96eb2/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3", size = 75375 }, + { url = "https://files.pythonhosted.org/packages/86/3b/d6b65da2b864822324745c0a73fe7fd86c67ccea54173682c3081d7adea8/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b", size = 75466 }, + { url = "https://files.pythonhosted.org/packages/f5/33/467a093bf004a70022cb410c590d937134bba2faa17bf9dc42a48f49af35/lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074", size = 25914 }, + { url = "https://files.pythonhosted.org/packages/77/ce/7956dc5ac2f8b62291b798c8363c81810e22a9effe469629d297d087e350/lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282", size = 27525 }, + { url = "https://files.pythonhosted.org/packages/be/11/23bcc3a85c9df7326d332b29172eaa088a3ebecb2674f257de2599e36aeb/lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a", size = 27427 }, + { url = "https://files.pythonhosted.org/packages/77/18/b78391424f3e35147b0e4d280dda0320c29ee9930b908e42fbe7920b2492/lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c", size = 66346 }, + { url = "https://files.pythonhosted.org/packages/b8/75/4669e1a7e7150e81ac27acc602ae61a37b4cc950c1ed3bd13b8d518bc026/lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255", size = 66369 }, + { url = "https://files.pythonhosted.org/packages/c8/a2/c99adb712e6ec8387d608c73d5b7a4a459c1c7813f38ee869f605bdc3f38/lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8", size = 69713 }, + { url = "https://files.pythonhosted.org/packages/f0/84/efe5dfb7c456bd3baa134dc2a4d7c891e7ce15a14c642cbfbcf50ff038ed/lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee", size = 70065 }, + { url = "https://files.pythonhosted.org/packages/36/15/f629f8aea93991a7e4700ef2601e5e4ed3fd0bcd106e3dc69c4ab12ffb98/lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4", size = 25773 }, + { url = "https://files.pythonhosted.org/packages/2b/ac/1ce4d58a508a74e4c6aa1e954e77be60720f5717c01b7b34f91ee6015837/lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696", size = 27536 }, + { url = "https://files.pythonhosted.org/packages/bc/2f/b9230d00c2eaa629e67cc69f285bf6b5692cb1d0179a1f8764edd451da86/lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94", size = 27431 }, + { url = "https://files.pythonhosted.org/packages/20/44/7d3b51ada1ddf873b136e2fa1d68bf3ee7b406b0bd9eeb97445932e2bfe1/lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b", size = 67547 }, + { url = "https://files.pythonhosted.org/packages/ab/be/d0a76dd4404ee68c7dd611c9b48e58b5c70ac5458e4c951b2c8923c24dd9/lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757", size = 67068 }, + { url = "https://files.pythonhosted.org/packages/d4/f8/d2d0d5caadf41c2d1fc9044dfc0e10d2831fb4ab6a077e68d25ea5bbff3b/lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424", size = 68587 }, + { url = "https://files.pythonhosted.org/packages/8e/ae/3e15cffacbdb64ac49930cdbc23cb0c67e1bb9e8a8ca7765fd8a8d2510c3/lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658", size = 68949 }, + { url = "https://files.pythonhosted.org/packages/36/40/471f9cbecae0ede81b61ef94687357396fe1b04d7a2090a122ad81093afe/lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70", size = 25772 }, + { url = "https://files.pythonhosted.org/packages/fe/30/40879041ed6a3364bfa862c4237aa7fe94dcd4affa2175718acbbf4d29b9/lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd", size = 27540 }, + { url = "https://files.pythonhosted.org/packages/31/8b/94dc8d58704ab87b39faed6f2fc0090b9d90e2e2aa2bbec35c79f3d2a054/lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d", size = 16405 }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206 }, + { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079 }, + { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620 }, + { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818 }, + { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493 }, + { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630 }, + { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745 }, + { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021 }, + { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659 }, + { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213 }, + { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 }, + { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 }, + { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 }, + { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 }, + { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 }, + { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 }, + { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 }, + { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 }, + { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 }, + { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 }, + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 }, + { url = "https://files.pythonhosted.org/packages/f8/ff/2c942a82c35a49df5de3a630ce0a8456ac2969691b230e530ac12314364c/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", size = 18192 }, + { url = "https://files.pythonhosted.org/packages/4f/14/6f294b9c4f969d0c801a4615e221c1e084722ea6114ab2114189c5b8cbe0/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", size = 14072 }, + { url = "https://files.pythonhosted.org/packages/81/d4/fd74714ed30a1dedd0b82427c02fa4deec64f173831ec716da11c51a50aa/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", size = 26928 }, + { url = "https://files.pythonhosted.org/packages/c7/bd/50319665ce81bb10e90d1cf76f9e1aa269ea6f7fa30ab4521f14d122a3df/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", size = 26106 }, + { url = "https://files.pythonhosted.org/packages/4c/6f/f2b0f675635b05f6afd5ea03c094557bdb8622fa8e673387444fe8d8e787/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68", size = 25781 }, + { url = "https://files.pythonhosted.org/packages/51/e0/393467cf899b34a9d3678e78961c2c8cdf49fb902a959ba54ece01273fb1/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", size = 30518 }, + { url = "https://files.pythonhosted.org/packages/f6/02/5437e2ad33047290dafced9df741d9efc3e716b75583bbd73a9984f1b6f7/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", size = 29669 }, + { url = "https://files.pythonhosted.org/packages/0e/7d/968284145ffd9d726183ed6237c77938c021abacde4e073020f920e060b2/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", size = 29933 }, + { url = "https://files.pythonhosted.org/packages/bf/f3/ecb00fc8ab02b7beae8699f34db9357ae49d9f21d4d3de6f305f34fa949e/MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", size = 16656 }, + { url = "https://files.pythonhosted.org/packages/92/21/357205f03514a49b293e214ac39de01fadd0970a6e05e4bf1ddd0ffd0881/MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", size = 17206 }, + { url = "https://files.pythonhosted.org/packages/0f/31/780bb297db036ba7b7bbede5e1d7f1e14d704ad4beb3ce53fb495d22bc62/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", size = 18193 }, + { url = "https://files.pythonhosted.org/packages/6c/77/d77701bbef72892affe060cdacb7a2ed7fd68dae3b477a8642f15ad3b132/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", size = 14073 }, + { url = "https://files.pythonhosted.org/packages/d9/a7/1e558b4f78454c8a3a0199292d96159eb4d091f983bc35ef258314fe7269/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", size = 26486 }, + { url = "https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", size = 25685 }, + { url = "https://files.pythonhosted.org/packages/6a/18/ae5a258e3401f9b8312f92b028c54d7026a97ec3ab20bfaddbdfa7d8cce8/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", size = 25338 }, + { url = "https://files.pythonhosted.org/packages/0b/cc/48206bd61c5b9d0129f4d75243b156929b04c94c09041321456fd06a876d/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", size = 30439 }, + { url = "https://files.pythonhosted.org/packages/d1/06/a41c112ab9ffdeeb5f77bc3e331fdadf97fa65e52e44ba31880f4e7f983c/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", size = 29531 }, + { url = "https://files.pythonhosted.org/packages/02/8c/ab9a463301a50dab04d5472e998acbd4080597abc048166ded5c7aa768c8/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", size = 29823 }, + { url = "https://files.pythonhosted.org/packages/bc/29/9bc18da763496b055d8e98ce476c8e718dcfd78157e17f555ce6dd7d0895/MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", size = 16658 }, + { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, + { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344 }, + { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389 }, + { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607 }, + { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728 }, + { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826 }, + { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843 }, + { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219 }, + { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946 }, + { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063 }, + { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, +] + +[[package]] +name = "matplotlib" +version = "3.7.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "contourpy", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "cycler", marker = "python_full_version < '3.9'" }, + { name = "fonttools", marker = "python_full_version < '3.9'" }, + { name = "importlib-resources", version = "6.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "packaging", marker = "python_full_version < '3.9'" }, + { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pyparsing", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "python-dateutil", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/f0/3836719cc3982fbba3b840d18a59db1d0ee9ac7986f24e8c0a092851b67b/matplotlib-3.7.5.tar.gz", hash = "sha256:1e5c971558ebc811aa07f54c7b7c677d78aa518ef4c390e14673a09e0860184a", size = 38098611 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/b0/3808e86c41e5d97822d77e89d7f3cb0890725845c050d87ec53732a8b150/matplotlib-3.7.5-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:4a87b69cb1cb20943010f63feb0b2901c17a3b435f75349fd9865713bfa63925", size = 8322924 }, + { url = "https://files.pythonhosted.org/packages/5b/05/726623be56391ba1740331ad9f1cd30e1adec61c179ddac134957a6dc2e7/matplotlib-3.7.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d3ce45010fefb028359accebb852ca0c21bd77ec0f281952831d235228f15810", size = 7438436 }, + { url = "https://files.pythonhosted.org/packages/15/83/89cdef49ef1e320060ec951ba33c132df211561d866c3ed144c81fd110b2/matplotlib-3.7.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbea1e762b28400393d71be1a02144aa16692a3c4c676ba0178ce83fc2928fdd", size = 7341849 }, + { url = "https://files.pythonhosted.org/packages/94/29/39fc4acdc296dd86e09cecb65c14966e1cf18e0f091b9cbd9bd3f0c19ee4/matplotlib-3.7.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec0e1adc0ad70ba8227e957551e25a9d2995e319c29f94a97575bb90fa1d4469", size = 11354141 }, + { url = "https://files.pythonhosted.org/packages/54/36/44c5eeb0d83ae1e3ed34d264d7adee947c4fd56c4a9464ce822de094995a/matplotlib-3.7.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6738c89a635ced486c8a20e20111d33f6398a9cbebce1ced59c211e12cd61455", size = 11457668 }, + { url = "https://files.pythonhosted.org/packages/b7/e2/f68aeaedf0ef57cbb793637ee82e62e64ea26cee908db0fe4f8e24d502c0/matplotlib-3.7.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1210b7919b4ed94b5573870f316bca26de3e3b07ffdb563e79327dc0e6bba515", size = 11580088 }, + { url = "https://files.pythonhosted.org/packages/d9/f7/7c88d34afc38943aa5e4e04d27fc9da5289a48c264c0d794f60c9cda0949/matplotlib-3.7.5-cp310-cp310-win32.whl", hash = "sha256:068ebcc59c072781d9dcdb82f0d3f1458271c2de7ca9c78f5bd672141091e9e1", size = 7339332 }, + { url = "https://files.pythonhosted.org/packages/91/99/e5f6f7c9438279581c4a2308d264fe24dc98bb80e3b2719f797227e54ddc/matplotlib-3.7.5-cp310-cp310-win_amd64.whl", hash = "sha256:f098ffbaab9df1e3ef04e5a5586a1e6b1791380698e84938d8640961c79b1fc0", size = 7506405 }, + { url = "https://files.pythonhosted.org/packages/5e/c6/45d0485e59d70b7a6a81eade5d0aed548b42cc65658c0ce0f813b9249165/matplotlib-3.7.5-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:f65342c147572673f02a4abec2d5a23ad9c3898167df9b47c149f32ce61ca078", size = 8325506 }, + { url = "https://files.pythonhosted.org/packages/0e/0a/83bd8589f3597745f624fbcc7da1140088b2f4160ca51c71553c561d0df5/matplotlib-3.7.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4ddf7fc0e0dc553891a117aa083039088d8a07686d4c93fb8a810adca68810af", size = 7439905 }, + { url = "https://files.pythonhosted.org/packages/84/c1/a7705b24f8f9b4d7ceea0002c13bae50cf9423f299f56d8c47a5cd2627d2/matplotlib-3.7.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ccb830fc29442360d91be48527809f23a5dcaee8da5f4d9b2d5b867c1b087b8", size = 7342895 }, + { url = "https://files.pythonhosted.org/packages/94/6e/55d7d8310c96a7459c883aa4be3f5a9338a108278484cbd5c95d480d1cef/matplotlib-3.7.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efc6bb28178e844d1f408dd4d6341ee8a2e906fc9e0fa3dae497da4e0cab775d", size = 11358830 }, + { url = "https://files.pythonhosted.org/packages/55/57/3b36afe104216db1cf2f3889c394b403ea87eda77c4815227c9524462ba8/matplotlib-3.7.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b15c4c2d374f249f324f46e883340d494c01768dd5287f8bc00b65b625ab56c", size = 11462575 }, + { url = "https://files.pythonhosted.org/packages/f3/0b/fabcf5f66b12fab5c4110d06a6c0fed875c7e63bc446403f58f9dadc9999/matplotlib-3.7.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d028555421912307845e59e3de328260b26d055c5dac9b182cc9783854e98fb", size = 11584280 }, + { url = "https://files.pythonhosted.org/packages/47/a9/1ad7df27a9da70b62109584632f83fe6ef45774701199c44d5777107c240/matplotlib-3.7.5-cp311-cp311-win32.whl", hash = "sha256:fe184b4625b4052fa88ef350b815559dd90cc6cc8e97b62f966e1ca84074aafa", size = 7340429 }, + { url = "https://files.pythonhosted.org/packages/e3/b1/1b6c34b89173d6c206dc5a4028e8518b4dfee3569c13bdc0c88d0486cae7/matplotlib-3.7.5-cp311-cp311-win_amd64.whl", hash = "sha256:084f1f0f2f1010868c6f1f50b4e1c6f2fb201c58475494f1e5b66fed66093647", size = 7507112 }, + { url = "https://files.pythonhosted.org/packages/75/dc/4e341a3ef36f3e7321aec0741317f12c7a23264be708a97972bf018c34af/matplotlib-3.7.5-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:34bceb9d8ddb142055ff27cd7135f539f2f01be2ce0bafbace4117abe58f8fe4", size = 8323797 }, + { url = "https://files.pythonhosted.org/packages/af/83/bbb482d678362ceb68cc59ec4fc705dde636025969361dac77be868541ef/matplotlib-3.7.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c5a2134162273eb8cdfd320ae907bf84d171de948e62180fa372a3ca7cf0f433", size = 7439549 }, + { url = "https://files.pythonhosted.org/packages/1a/ee/e49a92d9e369b2b9e4373894171cb4e641771cd7f81bde1d8b6fb8c60842/matplotlib-3.7.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:039ad54683a814002ff37bf7981aa1faa40b91f4ff84149beb53d1eb64617980", size = 7341788 }, + { url = "https://files.pythonhosted.org/packages/48/79/89cb2fc5ddcfc3d440a739df04dbe6e4e72b1153d1ebd32b45d42eb71d27/matplotlib-3.7.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d742ccd1b09e863b4ca58291728db645b51dab343eebb08d5d4b31b308296ce", size = 11356329 }, + { url = "https://files.pythonhosted.org/packages/ff/25/84f181cdae5c9eba6fd1c2c35642aec47233425fe3b0d6fccdb323fb36e0/matplotlib-3.7.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:743b1c488ca6a2bc7f56079d282e44d236bf375968bfd1b7ba701fd4d0fa32d6", size = 11577813 }, + { url = "https://files.pythonhosted.org/packages/9f/24/b2db065d40e58033b3350222fb8bbb0ffcb834029df9c1f9349dd9c7dd45/matplotlib-3.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:fbf730fca3e1f23713bc1fae0a57db386e39dc81ea57dc305c67f628c1d7a342", size = 7507667 }, + { url = "https://files.pythonhosted.org/packages/e3/72/50a38c8fd5dc845b06f8e71c9da802db44b81baabf4af8be78bb8a5622ea/matplotlib-3.7.5-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:cfff9b838531698ee40e40ea1a8a9dc2c01edb400b27d38de6ba44c1f9a8e3d2", size = 8322659 }, + { url = "https://files.pythonhosted.org/packages/b1/ea/129163dcd21db6da5d559a8160c4a74c1dc5f96ac246a3d4248b43c7648d/matplotlib-3.7.5-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:1dbcca4508bca7847fe2d64a05b237a3dcaec1f959aedb756d5b1c67b770c5ee", size = 7438408 }, + { url = "https://files.pythonhosted.org/packages/aa/59/4d13e5b6298b1ca5525eea8c68d3806ae93ab6d0bb17ca9846aa3156b92b/matplotlib-3.7.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4cdf4ef46c2a1609a50411b66940b31778db1e4b73d4ecc2eaa40bd588979b13", size = 7341782 }, + { url = "https://files.pythonhosted.org/packages/9e/c4/f562df04b08487731743511ff274ae5d31dce2ff3e5621f8b070d20ab54a/matplotlib-3.7.5-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:167200ccfefd1674b60e957186dfd9baf58b324562ad1a28e5d0a6b3bea77905", size = 9196487 }, + { url = "https://files.pythonhosted.org/packages/30/33/cc27211d2ffeee4fd7402dca137b6e8a83f6dcae3d4be8d0ad5068555561/matplotlib-3.7.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:53e64522934df6e1818b25fd48cf3b645b11740d78e6ef765fbb5fa5ce080d02", size = 9213051 }, + { url = "https://files.pythonhosted.org/packages/9b/9d/8bd37c86b79312c9dbcfa379dec32303f9b38e8456e0829d7e666a0e0a05/matplotlib-3.7.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e3bc79b2d7d615067bd010caff9243ead1fc95cf735c16e4b2583173f717eb", size = 11370807 }, + { url = "https://files.pythonhosted.org/packages/c0/1e/b24a07a849c8d458f1b3724f49029f0dedf748bdedb4d5f69491314838b6/matplotlib-3.7.5-cp38-cp38-win32.whl", hash = "sha256:6b641b48c6819726ed47c55835cdd330e53747d4efff574109fd79b2d8a13748", size = 7340461 }, + { url = "https://files.pythonhosted.org/packages/16/51/58b0b9de42fe1e665736d9286f88b5f1556a0e22bed8a71f468231761083/matplotlib-3.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:f0b60993ed3488b4532ec6b697059897891927cbfc2b8d458a891b60ec03d9d7", size = 7507471 }, + { url = "https://files.pythonhosted.org/packages/0d/00/17487e9e8949ca623af87f6c8767408efe7530b7e1f4d6897fa7fa940834/matplotlib-3.7.5-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:090964d0afaff9c90e4d8de7836757e72ecfb252fb02884016d809239f715651", size = 8323175 }, + { url = "https://files.pythonhosted.org/packages/6a/84/be0acd521fa9d6697657cf35878153f8009a42b4b75237aebc302559a8a9/matplotlib-3.7.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9fc6fcfbc55cd719bc0bfa60bde248eb68cf43876d4c22864603bdd23962ba25", size = 7438737 }, + { url = "https://files.pythonhosted.org/packages/17/39/175f36a6d68d0cf47a4fecbae9728048355df23c9feca8688f1476b198e6/matplotlib-3.7.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7cc3078b019bb863752b8b60e8b269423000f1603cb2299608231996bd9d54", size = 7341916 }, + { url = "https://files.pythonhosted.org/packages/36/c0/9a1c2a79f85c15d41b60877cbc333694ed80605e5c97a33880c4ecfd5bf1/matplotlib-3.7.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e4e9a868e8163abaaa8259842d85f949a919e1ead17644fb77a60427c90473c", size = 11352264 }, + { url = "https://files.pythonhosted.org/packages/a6/39/b0204e0e7a899b0676733366a55ccafa723799b719bc7f2e85e5ecde26a0/matplotlib-3.7.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa7ebc995a7d747dacf0a717d0eb3aa0f0c6a0e9ea88b0194d3a3cd241a1500f", size = 11454722 }, + { url = "https://files.pythonhosted.org/packages/d8/39/64dd1d36c79e72e614977db338d180cf204cf658927c05a8ef2d47feb4c0/matplotlib-3.7.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3785bfd83b05fc0e0c2ae4c4a90034fe693ef96c679634756c50fe6efcc09856", size = 11576343 }, + { url = "https://files.pythonhosted.org/packages/31/b4/e77bc11394d858bdf15e356980fceb4ac9604b0fa8212ef3ca4f1dc166b8/matplotlib-3.7.5-cp39-cp39-win32.whl", hash = "sha256:29b058738c104d0ca8806395f1c9089dfe4d4f0f78ea765c6c704469f3fffc81", size = 7340455 }, + { url = "https://files.pythonhosted.org/packages/4a/84/081820c596b9555ecffc6819ee71f847f2fbb0d7c70a42c1eeaa54edf3e0/matplotlib-3.7.5-cp39-cp39-win_amd64.whl", hash = "sha256:fd4028d570fa4b31b7b165d4a685942ae9cdc669f33741e388c01857d9723eab", size = 7507711 }, + { url = "https://files.pythonhosted.org/packages/27/6c/1bb10f3d6f337b9faa2e96a251bd87ba5fed85a608df95eb4d69acc109f0/matplotlib-3.7.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2a9a3f4d6a7f88a62a6a18c7e6a84aedcaf4faf0708b4ca46d87b19f1b526f88", size = 7397285 }, + { url = "https://files.pythonhosted.org/packages/b2/36/66cfea213e9ba91cda9e257542c249ed235d49021af71c2e8007107d7d4c/matplotlib-3.7.5-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b3fd853d4a7f008a938df909b96db0b454225f935d3917520305b90680579c", size = 7552612 }, + { url = "https://files.pythonhosted.org/packages/77/df/16655199bf984c37c6a816b854bc032b56aef521aadc04f27928422f3c91/matplotlib-3.7.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ad550da9f160737d7890217c5eeed4337d07e83ca1b2ca6535078f354e7675", size = 7515564 }, + { url = "https://files.pythonhosted.org/packages/5b/c8/3534c3705a677b71abb6be33609ba129fdeae2ea4e76b2fd3ab62c86fab3/matplotlib-3.7.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:20da7924a08306a861b3f2d1da0d1aa9a6678e480cf8eacffe18b565af2813e7", size = 7521336 }, + { url = "https://files.pythonhosted.org/packages/20/a0/c5c0d410798b387ed3a177a5a7eba21055dd9c41d4b15bd0861241a5a60e/matplotlib-3.7.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b45c9798ea6bb920cb77eb7306409756a7fab9db9b463e462618e0559aecb30e", size = 7397931 }, + { url = "https://files.pythonhosted.org/packages/c3/2f/9e9509727d4c7d1b8e2c88e9330a97d54a1dd20bd316a0c8d2f8b38c4513/matplotlib-3.7.5-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a99866267da1e561c7776fe12bf4442174b79aac1a47bd7e627c7e4d077ebd83", size = 7553224 }, + { url = "https://files.pythonhosted.org/packages/89/0c/5f3e403dcf5c23799c92b0139dd00e41caf23983e9281f5bfeba3065e7d2/matplotlib-3.7.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6aa62adb6c268fc87d80f963aca39c64615c31830b02697743c95590ce3fbb", size = 7513250 }, + { url = "https://files.pythonhosted.org/packages/87/e0/03eba0a8c3775ef910dbb3a287114a64c47abbcaeab2543c59957f155a86/matplotlib-3.7.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e530ab6a0afd082d2e9c17eb1eb064a63c5b09bb607b2b74fa41adbe3e162286", size = 7521729 }, +] + +[[package]] +name = "matplotlib" +version = "3.9.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "cycler", marker = "python_full_version == '3.9.*'" }, + { name = "fonttools", marker = "python_full_version == '3.9.*'" }, + { name = "importlib-resources", version = "6.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "packaging", marker = "python_full_version == '3.9.*'" }, + { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pyparsing", version = "3.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "python-dateutil", marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/17/1747b4154034befd0ed33b52538f5eb7752d05bb51c5e2a31470c3bc7d52/matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3", size = 36106529 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/4f/8c/b1f5bd2bd70e60f93b1b54c4d5ba7a992312021d0ddddf572f9a1a6d9348/matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d", size = 7828211 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/23/e1/4f0e237bf349c02ff9d1b6e7109f1a17f745263809b9714a8576dc17752b/matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3", size = 7838065 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/9f/8b/3d0c7a002db3b1ed702731c2a9a06d78d035f1f2fb0fb936a8e43cc1e9f4/matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b", size = 7841160 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/71/1f/c6e1daea55b7bfeb3d84c6cb1abc449f6a02b181e7e2a5e4db34c3afb793/matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799", size = 7841311 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/10/11/237f9c3a4e8d810b1759b67ff2da7c32c04f9c80aa475e7beb36ed43a8fb/matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965", size = 7896987 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/57/68/c2feb4667adbf882ffa4b3e0ac9967f848980d9f8b5bebd86644aa67ce6a/matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac", size = 7822962 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/5e/b6/5a1f868782cd13f053a679984e222007ecff654a9bfbac6b27a65f4eeb05/matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865", size = 7854624 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "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 = "cycler", marker = "python_full_version >= '3.10'" }, + { name = "fonttools", marker = "python_full_version >= '3.10'" }, + { name = "kiwisolver", version = "1.4.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyparsing", version = "3.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/b1/f70e27cf1cd76ce2a5e1aa5579d05afe3236052c6d9b9a96325bc823a17e/matplotlib-3.10.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff2ae14910be903f4a24afdbb6d7d3a6c44da210fc7d42790b87aeac92238a16", size = 8163654 }, + { url = "https://files.pythonhosted.org/packages/26/af/5ec3d4636106718bb62503a03297125d4514f98fe818461bd9e6b9d116e4/matplotlib-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0721a3fd3d5756ed593220a8b86808a36c5031fce489adb5b31ee6dbb47dd5b2", size = 8037943 }, + { url = "https://files.pythonhosted.org/packages/a1/3d/07f9003a71b698b848c9925d05979ffa94a75cd25d1a587202f0bb58aa81/matplotlib-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0673b4b8f131890eb3a1ad058d6e065fb3c6e71f160089b65f8515373394698", size = 8449510 }, + { url = "https://files.pythonhosted.org/packages/12/87/9472d4513ff83b7cd864311821793ab72234fa201ab77310ec1b585d27e2/matplotlib-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e875b95ac59a7908978fe307ecdbdd9a26af7fa0f33f474a27fcf8c99f64a19", size = 8586585 }, + { url = "https://files.pythonhosted.org/packages/31/9e/fe74d237d2963adae8608faeb21f778cf246dbbf4746cef87cffbc82c4b6/matplotlib-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2589659ea30726284c6c91037216f64a506a9822f8e50592d48ac16a2f29e044", size = 9397911 }, + { url = "https://files.pythonhosted.org/packages/b6/1b/025d3e59e8a4281ab463162ad7d072575354a1916aba81b6a11507dfc524/matplotlib-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a97ff127f295817bc34517255c9db6e71de8eddaab7f837b7d341dee9f2f587f", size = 8052998 }, + { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669 }, + { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996 }, + { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612 }, + { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258 }, + { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896 }, + { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281 }, + { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488 }, + { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264 }, + { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048 }, + { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111 }, + { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771 }, + { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742 }, + { url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112 }, + { url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931 }, + { url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422 }, + { url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819 }, + { url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782 }, + { url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812 }, + { url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021 }, + { url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782 }, + { url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901 }, + { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864 }, + { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487 }, + { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832 }, + { url = "https://files.pythonhosted.org/packages/c8/f6/10adb696d8cbeed2ab4c2e26ecf1c80dd3847bbf3891f4a0c362e0e08a5a/matplotlib-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:648406f1899f9a818cef8c0231b44dcfc4ff36f167101c3fd1c9151f24220fdc", size = 8158685 }, + { url = "https://files.pythonhosted.org/packages/3f/84/0603d917406072763e7f9bb37747d3d74d7ecd4b943a8c947cc3ae1cf7af/matplotlib-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:02582304e352f40520727984a5a18f37e8187861f954fea9be7ef06569cf85b4", size = 8035491 }, + { url = "https://files.pythonhosted.org/packages/fd/7d/6a8b31dd07ed856b3eae001c9129670ef75c4698fa1c2a6ac9f00a4a7054/matplotlib-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3809916157ba871bcdd33d3493acd7fe3037db5daa917ca6e77975a94cef779", size = 8590087 }, +] + +[[package]] +name = "mccabe" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/18/fa675aa501e11d6d6ca0ae73a101b2f3571a565e0f7d38e062eec18a91ee/mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f", size = 8612 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", size = 8556 }, +] + +[[package]] +name = "metrohash-python" +version = "1.1.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/b5/4002d16e71d6a4b1c14a37d204e25aba6508b12915fe0dd07fa703ca0fa1/metrohash-python-1.1.3.3.tar.gz", hash = "sha256:2003c9b2d07514c8228d901e3b32492ae42459e577e946ab91c0ef6eddfd0926", size = 45994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/07/aa99c165850f2250cfb2c1311abe36ade678ad2c4f7b02339068fc5f940a/metrohash_python-1.1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1a5076239cf18057c92c0c8c14164a2863cfeaf8372e8af98b238c730137801", size = 27447 }, + { url = "https://files.pythonhosted.org/packages/04/77/7efaa8d0e9219b448af1865e2140fe583032f836284f1846fc950744e909/metrohash_python-1.1.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b8cbef4169a152d9fdffee4145aa2f30f38ebc00e6e01fdb58a550e7b67bf31", size = 147878 }, + { url = "https://files.pythonhosted.org/packages/4f/85/b2e3a06b4f6b6093a95117072f16ccee0e5001139fd988a7b9e840a5f5bc/metrohash_python-1.1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2214b48d8e7663223a358951df4b0ce98493be660face2536a146957eb80674c", size = 149168 }, + { url = "https://files.pythonhosted.org/packages/a8/b0/da7928dce6ecc35aeff3f16a13a8d5a5924ccbdaeb911a5f8c162c5bfd89/metrohash_python-1.1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a6e9ea7d87ff48d0021fe88666f399b3173fa4e9545e4de1120d8a72aa185711", size = 752520 }, + { url = "https://files.pythonhosted.org/packages/ba/c6/f88a22c6c788457965e486e5dcb2b0f775de1d5a210a6d863b63ff4b47f6/metrohash_python-1.1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d70fb42aae512538c91d43a1c2edcea875c80919a78ce8e22fedd07a3109f1d1", size = 694849 }, + { url = "https://files.pythonhosted.org/packages/81/80/65cde9cefd3585985a536666396f1f9b9bdffd3bcc3f4266ccfd8e01e38f/metrohash_python-1.1.3.3-cp310-cp310-win32.whl", hash = "sha256:a2def24d47f0f226c4278dd30f51498a57199c64cc8432989d67ef11b4e518aa", size = 24596 }, + { url = "https://files.pythonhosted.org/packages/85/9f/3747be69c137cea8c547691e4eb8f1da84b72976d743c4f688c1174ede63/metrohash_python-1.1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:0bc6a8429ff017f2d8cb6ffb76d5355f9c6788010667470ab7a1b2b10252d60a", size = 24493 }, + { url = "https://files.pythonhosted.org/packages/2a/12/81b8a717dc01925dfa7eaa3f8e71c879b14713d32ea7998860d16b3b316f/metrohash_python-1.1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ff513c7eddca391a02f23fafb0dfbf6aef80102f19db5d2abda3aee7534488c", size = 26952 }, + { url = "https://files.pythonhosted.org/packages/96/27/cf7d9ac0c25868176799bc3a8298ef7d51dddd93799404a3413439e5579b/metrohash_python-1.1.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f2ae11b283f92f1324fb676a0ba8a7c4b4fa48e30678e0b04ebea49d40cbb7", size = 146966 }, + { url = "https://files.pythonhosted.org/packages/88/39/03274c4508fca778003f689ef89b68a862c8f0f5f8d8ee829ad88405d704/metrohash_python-1.1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbd744f62301ca716891c8bdd546c0fe951f59eb36c0ba03f2c0ccbc5ea5ca04", size = 148769 }, + { url = "https://files.pythonhosted.org/packages/c2/f1/65f44a55e4acbbd7929676c54874af061284feb13669b0d49b5917868397/metrohash_python-1.1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:393521a47116964ba37935503c1fb04d596c608f32123b12317148618659bb6f", size = 753590 }, + { url = "https://files.pythonhosted.org/packages/89/28/2cdd4874fd857d1b676baa56a352ff30364a60d739c5471f954a0cb541d5/metrohash_python-1.1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:292daf3debc95787cc2cab881ee97079fe94bdc31de89ef7af44b115bdee2fa9", size = 695716 }, + { url = "https://files.pythonhosted.org/packages/7e/56/7b9a7eb84d83c8b2d146e8c9d486153136cd4ab17fb19485f02b042d53eb/metrohash_python-1.1.3.3-cp38-cp38-win32.whl", hash = "sha256:5ff83cd43f2e2cec81d0026295a2320aa11a176c859e058a618b7fb08065c467", size = 24635 }, + { url = "https://files.pythonhosted.org/packages/64/c8/d700755c2afb6c0ac6e0d1204887ccdcda09efe9655c5270b79ab70391d2/metrohash_python-1.1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:a080987405c1cff83fd4449a753724be630e69622f181c24157ad52fb4a20115", size = 24533 }, + { url = "https://files.pythonhosted.org/packages/8e/32/968149f9bf2a98d1e1a4058547e8b0fc7dc0d7f752765ce6552cf68d35fc/metrohash_python-1.1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5070a1fe73649adc19d3604ed897d520c25b858a671a5661e0a669950521a0d4", size = 27448 }, + { url = "https://files.pythonhosted.org/packages/37/31/5f0b7b68acdeb2b0aa433c89f59e4d10d8e64557b7cb551ad27f01d68ccb/metrohash_python-1.1.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0b6f00066d92c7350521e4c42770afef0929507f002aa1c213b76b58318ac30", size = 146035 }, + { url = "https://files.pythonhosted.org/packages/07/7c/1103bc8046ced08db78a09c28c36efa35f060e2e5a00df3d0a34c3bd310e/metrohash_python-1.1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c5243c13a542586f673ae17a0a2dd04e2c11a55b65dd6a5e391617259f7f1c7", size = 147862 }, + { url = "https://files.pythonhosted.org/packages/8e/7f/2608f6c2aecec7c6271bfffd2241a8ba89508d4cf37ef10686bafe2ea0b5/metrohash_python-1.1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ec25885e7c74d668cef349538bd52250a7ec543f08f4e4c940db1f4b4c657c09", size = 751025 }, + { url = "https://files.pythonhosted.org/packages/62/c0/7bf43ca88f08843182465eee9b2305ca9efe6fe3adad738a8fe3b99d6f0a/metrohash_python-1.1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0e50c9c80a65b29990cf3c44af8b758c79479c66465213cbf924e2b0fce0ebe7", size = 693242 }, + { url = "https://files.pythonhosted.org/packages/01/c3/25c8628ce452840ad9f068172f365477e330f63e05e55d4d13c278805146/metrohash_python-1.1.3.3-cp39-cp39-win32.whl", hash = "sha256:6a99e89c5e0f16e01b07cdac19146ea0e86ff104ff49a3c2bbb80de3f317d3f9", size = 24560 }, + { url = "https://files.pythonhosted.org/packages/f4/18/ca01799efbed0c638af9b3956996c6ae9d135230bbf4358d486d8d2a4d18/metrohash_python-1.1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:8e4bb9e332fb397d5fabaee9ba84c37765abe50c7f633d126a8725711b3b0330", size = 24503 }, + { url = "https://files.pythonhosted.org/packages/ca/74/bbaef04e718acad2e8846bbdb112733915a368202546ef35ee2030de4c88/metrohash_python-1.1.3.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:db7e9c068c641287b38b7555c3c3a27d1ce57f1042bc0013ee36c683ae6221a8", size = 45863 }, + { url = "https://files.pythonhosted.org/packages/92/ae/ddd182b33e11b51577811ab855fd0423307dbbe8ac10390ada8295147a8e/metrohash_python-1.1.3.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e78077c597626cd6e90e3445b9dfa5d04ca7a064edb77ff15f0e808d30477c1c", size = 172194 }, + { url = "https://files.pythonhosted.org/packages/73/57/9b8e2273e01d229715bf0d7b8cd0a62273438e4a7b0463fb431db289cb55/metrohash_python-1.1.3.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64c98f0a76a92fe2e20118dfe490ec58a0080ad181e18d6e1e6d2c523d9b2cb6", size = 177579 }, + { url = "https://files.pythonhosted.org/packages/fd/23/a5e7d343ea99c5eb1883f2f0440cdb1d4f03c919c589352163dbfbc9afd1/metrohash_python-1.1.3.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1b986b902dd424fe69981ceafcc776ba52db61a13430ebf7bc7ccb76771ae84f", size = 42845 }, +] + +[[package]] +name = "networkx" +version = "3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/47b974da1a73f063c158a1f4cc33ed0abf7c04f98a19050e80c533c31f0c/networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61", size = 2021691 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/05/9d4f9b78ead6b2661d6e8ea772e111fc4a9fbd866ad0c81906c11206b55e/networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36", size = 2072251 }, +] + +[[package]] +name = "networkx" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772 }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + +[[package]] +name = "numpy" +version = "1.24.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", size = 10911229 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", size = 19789140 }, + { url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", size = 13854297 }, + { url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", size = 13995611 }, + { url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", size = 17282357 }, + { url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", size = 12429222 }, + { url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", size = 14841514 }, + { url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", size = 19775508 }, + { url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", size = 13840033 }, + { url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", size = 13991951 }, + { url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", size = 17278923 }, + { url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", size = 12422446 }, + { url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", size = 14834466 }, + { url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", size = 19780722 }, + { url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", size = 13843102 }, + { url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", size = 14039616 }, + { url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", size = 17316263 }, + { url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", size = 12455660 }, + { url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", size = 14868112 }, + { url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", size = 19816549 }, + { url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", size = 13879950 }, + { url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9", size = 14030228 }, + { url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", size = 17311170 }, + { url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", size = 12454918 }, + { url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", size = 14867441 }, + { url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", size = 19156590 }, + { url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", size = 16705744 }, + { url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", size = 14734290 }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, + { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, + { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, + { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, + { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, + { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, + { url = "https://files.pythonhosted.org/packages/7d/24/ce71dc08f06534269f66e73c04f5709ee024a1afe92a7b6e1d73f158e1f8/numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", size = 20636301 }, + { url = "https://files.pythonhosted.org/packages/ae/8c/ab03a7c25741f9ebc92684a20125fbc9fc1b8e1e700beb9197d750fdff88/numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", size = 13971216 }, + { url = "https://files.pythonhosted.org/packages/6d/64/c3bcdf822269421d85fe0d64ba972003f9bb4aa9a419da64b86856c9961f/numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", size = 14226281 }, + { url = "https://files.pythonhosted.org/packages/54/30/c2a907b9443cf42b90c17ad10c1e8fa801975f01cb9764f3f8eb8aea638b/numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", size = 18249516 }, + { url = "https://files.pythonhosted.org/packages/43/12/01a563fc44c07095996d0129b8899daf89e4742146f7044cdbdb3101c57f/numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", size = 13882132 }, + { url = "https://files.pythonhosted.org/packages/16/ee/9df80b06680aaa23fc6c31211387e0db349e0e36d6a63ba3bd78c5acdf11/numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", size = 18084181 }, + { url = "https://files.pythonhosted.org/packages/28/7d/4b92e2fe20b214ffca36107f1a3e75ef4c488430e64de2d9af5db3a4637d/numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", size = 5976360 }, + { url = "https://files.pythonhosted.org/packages/b5/42/054082bd8220bbf6f297f982f0a8f5479fcbc55c8b511d928df07b965869/numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", size = 15814633 }, + { url = "https://files.pythonhosted.org/packages/3f/72/3df6c1c06fc83d9cfe381cccb4be2532bbd38bf93fbc9fad087b6687f1c0/numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", size = 20455961 }, + { url = "https://files.pythonhosted.org/packages/8e/02/570545bac308b58ffb21adda0f4e220ba716fb658a63c151daecc3293350/numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", size = 18061071 }, + { url = "https://files.pythonhosted.org/packages/f4/5f/fafd8c51235f60d49f7a88e2275e13971e90555b67da52dd6416caec32fe/numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", size = 15709730 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pillow" +version = "10.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271 }, + { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658 }, + { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075 }, + { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808 }, + { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290 }, + { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163 }, + { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100 }, + { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880 }, + { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218 }, + { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487 }, + { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219 }, + { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265 }, + { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655 }, + { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304 }, + { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804 }, + { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126 }, + { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541 }, + { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616 }, + { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802 }, + { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213 }, + { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498 }, + { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219 }, + { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350 }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980 }, + { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799 }, + { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973 }, + { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054 }, + { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484 }, + { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375 }, + { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773 }, + { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690 }, + { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951 }, + { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427 }, + { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 }, + { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 }, + { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 }, + { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 }, + { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 }, + { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 }, + { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 }, + { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 }, + { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 }, + { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 }, + { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 }, + { url = "https://files.pythonhosted.org/packages/56/70/f40009702a477ce87d8d9faaa4de51d6562b3445d7a314accd06e4ffb01d/pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", size = 3509213 }, + { url = "https://files.pythonhosted.org/packages/10/43/105823d233c5e5d31cea13428f4474ded9d961652307800979a59d6a4276/pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", size = 3375883 }, + { url = "https://files.pythonhosted.org/packages/3c/ad/7850c10bac468a20c918f6a5dbba9ecd106ea1cdc5db3c35e33a60570408/pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", size = 4330810 }, + { url = "https://files.pythonhosted.org/packages/84/4c/69bbed9e436ac22f9ed193a2b64f64d68fcfbc9f4106249dc7ed4889907b/pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", size = 4444341 }, + { url = "https://files.pythonhosted.org/packages/8f/4f/c183c63828a3f37bf09644ce94cbf72d4929b033b109160a5379c2885932/pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", size = 4356005 }, + { url = "https://files.pythonhosted.org/packages/fb/ad/435fe29865f98a8fbdc64add8875a6e4f8c97749a93577a8919ec6f32c64/pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", size = 4525201 }, + { url = "https://files.pythonhosted.org/packages/80/74/be8bf8acdfd70e91f905a12ae13cfb2e17c0f1da745c40141e26d0971ff5/pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", size = 4460635 }, + { url = "https://files.pythonhosted.org/packages/e4/90/763616e66dc9ad59c9b7fb58f863755e7934ef122e52349f62c7742b82d3/pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", size = 4590283 }, + { url = "https://files.pythonhosted.org/packages/69/66/03002cb5b2c27bb519cba63b9f9aa3709c6f7a5d3b285406c01f03fb77e5/pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", size = 2235185 }, + { url = "https://files.pythonhosted.org/packages/f2/75/3cb820b2812405fc7feb3d0deb701ef0c3de93dc02597115e00704591bc9/pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", size = 2554594 }, + { url = "https://files.pythonhosted.org/packages/31/85/955fa5400fa8039921f630372cfe5056eed6e1b8e0430ee4507d7de48832/pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", size = 3509283 }, + { url = "https://files.pythonhosted.org/packages/23/9c/343827267eb28d41cd82b4180d33b10d868af9077abcec0af9793aa77d2d/pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", size = 3375691 }, + { url = "https://files.pythonhosted.org/packages/60/a3/7ebbeabcd341eab722896d1a5b59a3df98c4b4d26cf4b0385f8aa94296f7/pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", size = 4328295 }, + { url = "https://files.pythonhosted.org/packages/32/3f/c02268d0c6fb6b3958bdda673c17b315c821d97df29ae6969f20fb49388a/pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", size = 4440810 }, + { url = "https://files.pythonhosted.org/packages/67/5d/1c93c8cc35f2fdd3d6cc7e4ad72d203902859a2867de6ad957d9b708eb8d/pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", size = 4352283 }, + { url = "https://files.pythonhosted.org/packages/bc/a8/8655557c9c7202b8abbd001f61ff36711cefaf750debcaa1c24d154ef602/pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", size = 4521800 }, + { url = "https://files.pythonhosted.org/packages/58/78/6f95797af64d137124f68af1bdaa13b5332da282b86031f6fa70cf368261/pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", size = 4459177 }, + { url = "https://files.pythonhosted.org/packages/8a/6d/2b3ce34f1c4266d79a78c9a51d1289a33c3c02833fe294ef0dcbb9cba4ed/pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", size = 4589079 }, + { url = "https://files.pythonhosted.org/packages/e3/e0/456258c74da1ff5bf8ef1eab06a95ca994d8b9ed44c01d45c3f8cbd1db7e/pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", size = 2235247 }, + { url = "https://files.pythonhosted.org/packages/37/f8/bef952bdb32aa53741f58bf21798642209e994edc3f6598f337f23d5400a/pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", size = 2554479 }, + { url = "https://files.pythonhosted.org/packages/bb/8e/805201619cad6651eef5fc1fdef913804baf00053461522fabbc5588ea12/pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", size = 2243226 }, + { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889 }, + { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160 }, + { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020 }, + { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539 }, + { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125 }, + { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373 }, + { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661 }, + { url = "https://files.pythonhosted.org/packages/e1/1f/5a9fcd6ced51633c22481417e11b1b47d723f64fb536dfd67c015eb7f0ab/pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", size = 3493850 }, + { url = "https://files.pythonhosted.org/packages/cb/e6/3ea4755ed5320cb62aa6be2f6de47b058c6550f752dd050e86f694c59798/pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", size = 3346118 }, + { url = "https://files.pythonhosted.org/packages/0a/22/492f9f61e4648422b6ca39268ec8139277a5b34648d28f400faac14e0f48/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", size = 3434958 }, + { url = "https://files.pythonhosted.org/packages/f9/19/559a48ad4045704bb0547965b9a9345f5cd461347d977a56d178db28819e/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", size = 3490340 }, + { url = "https://files.pythonhosted.org/packages/d9/de/cebaca6fb79905b3a1aa0281d238769df3fb2ede34fd7c0caa286575915a/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", size = 3476048 }, + { url = "https://files.pythonhosted.org/packages/71/f0/86d5b2f04693b0116a01d75302b0a307800a90d6c351a8aa4f8ae76cd499/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", size = 3579366 }, + { url = "https://files.pythonhosted.org/packages/37/ae/2dbfc38cc4fd14aceea14bc440d5151b21f64c4c3ba3f6f4191610b7ee5d/pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", size = 2554652 }, +] + +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/8b/b158ad57ed44d3cc54db8d68ad7c0a58b8fc0e4c7a3f995f9d62d5b464a1/pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047", size = 3198442 }, + { url = "https://files.pythonhosted.org/packages/b1/f8/bb5d956142f86c2d6cc36704943fa761f2d2e4c48b7436fd0a85c20f1713/pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95", size = 3030553 }, + { url = "https://files.pythonhosted.org/packages/22/7f/0e413bb3e2aa797b9ca2c5c38cb2e2e45d88654e5b12da91ad446964cfae/pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61", size = 4405503 }, + { url = "https://files.pythonhosted.org/packages/f3/b4/cc647f4d13f3eb837d3065824aa58b9bcf10821f029dc79955ee43f793bd/pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1", size = 4490648 }, + { url = "https://files.pythonhosted.org/packages/c2/6f/240b772a3b35cdd7384166461567aa6713799b4e78d180c555bd284844ea/pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c", size = 4508937 }, + { url = "https://files.pythonhosted.org/packages/f3/5e/7ca9c815ade5fdca18853db86d812f2f188212792780208bdb37a0a6aef4/pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d", size = 4599802 }, + { url = "https://files.pythonhosted.org/packages/02/81/c3d9d38ce0c4878a77245d4cf2c46d45a4ad0f93000227910a46caff52f3/pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97", size = 4576717 }, + { url = "https://files.pythonhosted.org/packages/42/49/52b719b89ac7da3185b8d29c94d0e6aec8140059e3d8adcaa46da3751180/pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579", size = 4654874 }, + { url = "https://files.pythonhosted.org/packages/5b/0b/ede75063ba6023798267023dc0d0401f13695d228194d2242d5a7ba2f964/pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d", size = 2331717 }, + { url = "https://files.pythonhosted.org/packages/ed/3c/9831da3edea527c2ed9a09f31a2c04e77cd705847f13b69ca60269eec370/pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad", size = 2676204 }, + { url = "https://files.pythonhosted.org/packages/01/97/1f66ff8a1503d8cbfc5bae4dc99d54c6ec1e22ad2b946241365320caabc2/pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2", size = 2414767 }, + { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450 }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550 }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018 }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006 }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773 }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069 }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460 }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304 }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809 }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338 }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918 }, + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185 }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306 }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121 }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707 }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921 }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523 }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836 }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390 }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309 }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768 }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087 }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, + { url = "https://files.pythonhosted.org/packages/21/3a/c1835d1c7cf83559e95b4f4ed07ab0bb7acc689712adfce406b3f456e9fd/pillow-11.2.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7491cf8a79b8eb867d419648fff2f83cb0b3891c8b36da92cc7f1931d46108c8", size = 3198391 }, + { url = "https://files.pythonhosted.org/packages/b6/4d/dcb7a9af3fc1e8653267c38ed622605d9d1793349274b3ef7af06457e257/pillow-11.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b02d8f9cb83c52578a0b4beadba92e37d83a4ef11570a8688bbf43f4ca50909", size = 3030573 }, + { url = "https://files.pythonhosted.org/packages/9d/29/530ca098c1a1eb31d4e163d317d0e24e6d2ead907991c69ca5b663de1bc5/pillow-11.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:014ca0050c85003620526b0ac1ac53f56fc93af128f7546623cc8e31875ab928", size = 4398677 }, + { url = "https://files.pythonhosted.org/packages/8b/ee/0e5e51db34de1690264e5f30dcd25328c540aa11d50a3bc0b540e2a445b6/pillow-11.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3692b68c87096ac6308296d96354eddd25f98740c9d2ab54e1549d6c8aea9d79", size = 4484986 }, + { url = "https://files.pythonhosted.org/packages/93/7d/bc723b41ce3d2c28532c47678ec988974f731b5c6fadd5b3a4fba9015e4f/pillow-11.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f781dcb0bc9929adc77bad571b8621ecb1e4cdef86e940fe2e5b5ee24fd33b35", size = 4501897 }, + { url = "https://files.pythonhosted.org/packages/be/0b/532e31abc7389617ddff12551af625a9b03cd61d2989fa595e43c470ec67/pillow-11.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2b490402c96f907a166615e9a5afacf2519e28295f157ec3a2bb9bd57de638cb", size = 4592618 }, + { url = "https://files.pythonhosted.org/packages/4c/f0/21ed6499a6216fef753e2e2254a19d08bff3747108ba042422383f3e9faa/pillow-11.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6b20b93b3ccc9c1b597999209e4bc5cf2853f9ee66e3fc9a400a78733ffc9a", size = 4570493 }, + { url = "https://files.pythonhosted.org/packages/68/de/17004ddb8ab855573fe1127ab0168d11378cdfe4a7ee2a792a70ff2e9ba7/pillow-11.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b835d89c08a6c2ee7781b8dd0a30209a8012b5f09c0a665b65b0eb3560b6f36", size = 4647748 }, + { url = "https://files.pythonhosted.org/packages/c7/23/82ecb486384bb3578115c509d4a00bb52f463ee700a5ca1be53da3c88c19/pillow-11.2.1-cp39-cp39-win32.whl", hash = "sha256:b10428b3416d4f9c61f94b494681280be7686bda15898a3a9e08eb66a6d92d67", size = 2331731 }, + { url = "https://files.pythonhosted.org/packages/58/bb/87efd58b3689537a623d44dbb2550ef0bb5ff6a62769707a0fe8b1a7bdeb/pillow-11.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ebce70c3f486acf7591a3d73431fa504a4e18a9b97ff27f5f47b7368e4b9dd1", size = 2676346 }, + { url = "https://files.pythonhosted.org/packages/80/08/dc268475b22887b816e5dcfae31bce897f524b4646bab130c2142c9b2400/pillow-11.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:c27476257b2fdcd7872d54cfd119b3a9ce4610fb85c8e32b70b42e3680a29a1e", size = 2414623 }, + { url = "https://files.pythonhosted.org/packages/33/49/c8c21e4255b4f4a2c0c68ac18125d7f5460b109acc6dfdef1a24f9b960ef/pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156", size = 3181727 }, + { url = "https://files.pythonhosted.org/packages/6d/f1/f7255c0838f8c1ef6d55b625cfb286835c17e8136ce4351c5577d02c443b/pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772", size = 2999833 }, + { url = "https://files.pythonhosted.org/packages/e2/57/9968114457bd131063da98d87790d080366218f64fa2943b65ac6739abb3/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363", size = 3437472 }, + { url = "https://files.pythonhosted.org/packages/b2/1b/e35d8a158e21372ecc48aac9c453518cfe23907bb82f950d6e1c72811eb0/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0", size = 3459976 }, + { url = "https://files.pythonhosted.org/packages/26/da/2c11d03b765efff0ccc473f1c4186dc2770110464f2177efaed9cf6fae01/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01", size = 3527133 }, + { url = "https://files.pythonhosted.org/packages/79/1a/4e85bd7cadf78412c2a3069249a09c32ef3323650fd3005c97cca7aa21df/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193", size = 3571555 }, + { url = "https://files.pythonhosted.org/packages/69/03/239939915216de1e95e0ce2334bf17a7870ae185eb390fab6d706aadbfc0/pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013", size = 2674713 }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734 }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841 }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470 }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013 }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165 }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586 }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "py" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708 }, +] + +[[package]] +name = "pybtex" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "latexcodec" }, + { name = "pyyaml" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/9b/fd39836a6397fb363446d83075a7b9c2cc562f4c449292e039ed36084376/pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755", size = 402879 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/5f/40d8e90f985a05133a8895fc454c6127ecec3de8b095dd35bba91382f803/pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f", size = 561354 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385 }, +] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/b3/c832123f2699892c715fcdfebb1a8fdeffa11bb7b2350e46ecdd76b45a20/pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef", size = 103640 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/cc/227251b1471f129bc35e966bb0fceb005969023926d744139642d847b7ae/pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", size = 41725 }, +] + +[[package]] +name = "pyflakes" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/0f/0dc480da9162749bf629dca76570972dd9cce5bedc60196a3c912875c87d/pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db", size = 68567 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/11/2a745612f1d3cbbd9c69ba14b1b43a35a2f5c3c81cd0124508c52c64307f/pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", size = 68805 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pylint" +version = "2.17.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "platformdirs", version = "4.3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomlkit" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/e9/21f9ce3e4b81eef011be070a29f8a5c193e2488ed8713a898baa4e8b3362/pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad", size = 434994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/49/cea450a83079445a84f16050e571a7c383d3f474b13c5caedfebd4e35def/pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87", size = 537178 }, +] + +[[package]] +name = "pyparsing" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/83/08/13f3bce01b2061f2bbd582c9df82723de943784cf719a35ac886c652043a/pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032", size = 900231 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/0c/0e3c05b1c87bb6a1c76d281b0f35e78d2d80ac91b5f8f524cebf77f51049/pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", size = 104100 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, +] + +[[package]] +name = "pyproject-api" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version < '3.9'" }, + { name = "tomli", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/19/441e0624a8afedd15bbcce96df1b80479dd0ff0d965f5ce8fde4f2f6ffad/pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496", size = 22340 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/f4/3c4ddfcc0c19c217c6de513842d286de8021af2f2ab79bbb86c00342d778/pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228", size = 13100 }, +] + +[[package]] +name = "pyproject-api" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version >= '3.9'" }, + { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131 }, +] + +[[package]] +name = "pytest" +version = "6.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "atomicwrites", marker = "sys_platform == 'win32'" }, + { name = "attrs" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "py" }, + { name = "toml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/24/7d1f2d2537de114bdf1e6875115113ca80091520948d370c964b88070af2/pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", size = 1118720 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/76/86f886e750b81a4357b6ed606b2bcf0ce6d6c27ad3c09ebf63ed674fc86e/pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134", size = 280654 }, +] + +[[package]] +name = "pytest-cov" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, + { name = "toml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/3a/747e953051fd6eb5fb297907a825aad43d94c556d3b9938fc21f3172879f/pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7", size = 60395 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/84/576b071aef9ac9301e5c0ff35d117e12db50b87da6f12e745e9c5f745cc2/pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", size = 20441 }, +] + +[[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 } +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 }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "pywavelets" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/d4/008dceeb95fafcf141f39393bdfc10921d0b62a325c2794ac533195a1eb3/PyWavelets-1.4.1.tar.gz", hash = "sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93", size = 4589677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/92/a78bf0c3d84afd9b17727cce122c3fdb3860a27bd67b32448c7e64301e7b/PyWavelets-1.4.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c", size = 4365967 }, + { url = "https://files.pythonhosted.org/packages/f3/66/2bbcad043383d7be3bca2155972adba1d06be3bc5536afbfa22f1cd99688/PyWavelets-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4", size = 4269548 }, + { url = "https://files.pythonhosted.org/packages/07/fe/90ab3b98dfeb2177e1b8c8ccdd4e777e35dfe0aa98723308bd8f1a97fd47/PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c", size = 6748302 }, + { url = "https://files.pythonhosted.org/packages/3e/fc/651024e8b6e69bef6def2cbe27d520309f4ffc56b8d4885ab7046e1edc6c/PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202", size = 6836957 }, + { url = "https://files.pythonhosted.org/packages/51/af/53bcfea50c24cedb202b0c072193af94a1a611b26ab360082791e455b43f/PyWavelets-1.4.1-cp310-cp310-win32.whl", hash = "sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd", size = 4103794 }, + { url = "https://files.pythonhosted.org/packages/35/12/f1a4f72b5d71497e4200e71e253cc747077d8570b55693faaa7b81fb6dff/PyWavelets-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b", size = 4162789 }, + { url = "https://files.pythonhosted.org/packages/13/e4/86bb218c7926e1da7a52e0696cab120a17c995933f08d8228d9aa83b44c5/PyWavelets-1.4.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875", size = 4349932 }, + { url = "https://files.pythonhosted.org/packages/94/73/4df43d2e18e68c7ea88177c1fa14a25b5813a51b4953dc94c21f2de039d5/PyWavelets-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de", size = 4256446 }, + { url = "https://files.pythonhosted.org/packages/1d/5e/97ff80a20fb22f723f0c3f6f5f407b12579a560abf7c3a8087d052993dd9/PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e", size = 6964351 }, + { url = "https://files.pythonhosted.org/packages/de/a1/cd8a30e061f858f219364554b19d4318276c677a51d956c55fb0b134e8b2/PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784", size = 7040415 }, + { url = "https://files.pythonhosted.org/packages/1d/a1/0f9356779440aaaa35ff82479c40a094419f19ab94a3d5f49e090398959b/PyWavelets-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1", size = 4101666 }, + { url = "https://files.pythonhosted.org/packages/e4/13/9a1632347677e1be27900d9dc922f19bc01440eb8b0c663cea63b35275fc/PyWavelets-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc", size = 4160676 }, + { url = "https://files.pythonhosted.org/packages/2f/52/080267790e23a5186185f2c26d7b774cee754387d1bcb116c7a45f3546f6/PyWavelets-1.4.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966", size = 4349347 }, + { url = "https://files.pythonhosted.org/packages/73/8c/6d50b8e2ee4d12373a63791ad742df1e30ddd5f0f8d1c000c5b6b3afb2c9/PyWavelets-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa", size = 4254092 }, + { url = "https://files.pythonhosted.org/packages/cd/c1/132756d0033b37f4013299ac048bf34d5094673712984edb9e90e8d8a179/PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc", size = 6854556 }, + { url = "https://files.pythonhosted.org/packages/88/4b/b2b2a6f51e47c091c221bfde976a01a7e5f20e7e5e6341b2b9c4db73d2ed/PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4", size = 6942852 }, + { url = "https://files.pythonhosted.org/packages/6c/92/7e900e574575358a5af6ad9f8378d889b1a21e2ba835bae9d0eb7efd505b/PyWavelets-1.4.1-cp38-cp38-win32.whl", hash = "sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd", size = 4111675 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/f80ff31e73385b886c35fb9fb1377849f9c43a3c1195ed8dc8ed8dc1bd88/PyWavelets-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2", size = 4172202 }, + { url = "https://files.pythonhosted.org/packages/9f/67/33b37d53da9d225301e30894db5083569aa670b446253b3906fc0e96119e/PyWavelets-1.4.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6", size = 4366511 }, + { url = "https://files.pythonhosted.org/packages/a0/32/eeeaa4de640a84e2cc35c25aea289367059abce0cac84a9987b139a2a25f/PyWavelets-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426", size = 4268409 }, + { url = "https://files.pythonhosted.org/packages/34/c0/a121306b618af45ff7d769e1bd45ed3d6c798dc7f0094e0b56735388d96e/PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b", size = 6824100 }, + { url = "https://files.pythonhosted.org/packages/5a/98/4549479a32972bdfdd5e75e168219e97f4dfaee535a8308efef7291e8398/PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356", size = 6908506 }, + { url = "https://files.pythonhosted.org/packages/0d/72/db0ef5ca311627f86de89a7af6055301c67490f4160e725cdbd32eea7700/PyWavelets-1.4.1-cp39-cp39-win32.whl", hash = "sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c", size = 4111933 }, + { url = "https://files.pythonhosted.org/packages/02/15/89951f559601fb6755f2231558c33c1b9cbba9e8526906cbc258e27eb53d/PyWavelets-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4", size = 4171580 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218 }, + { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067 }, + { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812 }, + { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531 }, + { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820 }, + { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514 }, + { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "urllib3", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "scikit-image" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "imageio", version = "2.35.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "lazy-loader", marker = "python_full_version < '3.9'" }, + { name = "networkx", version = "3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "packaging", marker = "python_full_version < '3.9'" }, + { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pywavelets", marker = "python_full_version < '3.9'" }, + { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "tifffile", version = "2023.7.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/c2/a54d5e6e2d6708e0722a1aaccef4b7cc1e6df6f76c8b4ce98cd6d0c332c3/scikit_image-0.21.0.tar.gz", hash = "sha256:b33e823c54e6f11873ea390ee49ef832b82b9f70752c8759efd09d5a4e3d87f0", size = 22720419 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a1/6bc36ba38fe9312271cce46cf2025fcc63be096131747a8f41522a57aaef/scikit_image-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:978ac3302252155a8556cdfe067bad2d18d5ccef4e91c2f727bc564ed75566bc", size = 12987373 }, + { url = "https://files.pythonhosted.org/packages/e0/f7/0ec3a2fbed785259176eb2eee7b254fc68c653028907602231cc8ba09da0/scikit_image-0.21.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:82c22e008527e5ee26ab08e3ce919998ef164d538ff30b9e5764b223cfda06b1", size = 12419386 }, + { url = "https://files.pythonhosted.org/packages/ee/5b/3fe767d6ef7cbcf4894355e5905665f99237c5de465a8ca959a05d2320bc/scikit_image-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd29d2631d3e975c377066acfc1f4cb2cc95e2257cf70e7fedfcb96441096e88", size = 13207325 }, + { url = "https://files.pythonhosted.org/packages/70/a9/a9f63dde69ac5a4451d8a0ebdde95824ec31aafcae1c77658a9058e27bb7/scikit_image-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6c12925ceb9f3aede555921e26642d601b2d37d1617002a2636f2cb5178ae2f", size = 13760602 }, + { url = "https://files.pythonhosted.org/packages/f3/93/65601f7577d6fd49ec23bf8fb58c04d8170b06a1544452ae2ea9f59bf11f/scikit_image-0.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f538d4de77e4f3225d068d9ea2965bed3f7dda7f457a8f89634fa22ffb9ad8c", size = 22777245 }, + { url = "https://files.pythonhosted.org/packages/08/53/f28cfb52248665b42db7e45a36ffc3a304fef46b308e5065fe2046e78daf/scikit_image-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec9bab6920ac43037d7434058b67b5778d42c60f67b8679239f48a471e7ed6f8", size = 12911110 }, + { url = "https://files.pythonhosted.org/packages/20/54/06f821fd78c24f7047629dc4c8ed948101fc91fdf660ee3263d870220ae8/scikit_image-0.21.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:a54720430dba833ffbb6dedd93d9f0938c5dd47d20ab9ba3e4e61c19d95f6f19", size = 12333056 }, + { url = "https://files.pythonhosted.org/packages/31/cf/9e8e819a8d90fb74ec183a0c3e8e182587929845c93779f99439cd270f10/scikit_image-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e40dd102da14cdadc09210f930b4556c90ff8f99cd9d8bcccf9f73a86c44245", size = 13204303 }, + { url = "https://files.pythonhosted.org/packages/22/c3/c5f3c351d6337a18d07c3fb04475626c106cd3dc3d59b85ec50d07656db0/scikit_image-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff5719c7eb99596a39c3e1d9b564025bae78ecf1da3ee6842d34f6965b5f1474", size = 13726119 }, + { url = "https://files.pythonhosted.org/packages/08/c0/8085c5fd2f7f7514a0c5031b666171d5828ac5b3c9cf5d0ecd19688d5407/scikit_image-0.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:146c3824253eee9ff346c4ad10cb58376f91aefaf4a4bb2fe11aa21691f7de76", size = 22753693 }, + { url = "https://files.pythonhosted.org/packages/35/e4/d5d1574d09f30a4df757edf4213ce8e764aebe0f1642475cf384f9fa33bb/scikit_image-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e1b09f81a99c9c390215929194847b3cd358550b4b65bb6e42c5393d69cb74a", size = 12886049 }, + { url = "https://files.pythonhosted.org/packages/62/9b/8fd51371f3fd4ce06092d1f4740ec5a874a996727117e076c03755e8c777/scikit_image-0.21.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:9f7b5fb4a22f0d5ae0fa13beeb887c925280590145cd6d8b2630794d120ff7c7", size = 12315195 }, + { url = "https://files.pythonhosted.org/packages/fa/2b/ffecc6f29b48d1d46dc3bb7b4c908490260c3a0d69ac2d248d846b90d505/scikit_image-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4814033717f0b6491fee252facb9df92058d6a72ab78dd6408a50f3915a88b8", size = 13304683 }, + { url = "https://files.pythonhosted.org/packages/33/29/1d696450464d6e13358d3ef185a1fb14a11181c5dab1eb2837c02be86373/scikit_image-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0d6ed6502cca0c9719c444caafa0b8cda0f9e29e01ca42f621a240073284be", size = 13870072 }, + { url = "https://files.pythonhosted.org/packages/d7/d1/a4c715ad640c9eb0daaa77c4ce561b06e086bec44cbc79083e3548b00b76/scikit_image-0.21.0-cp38-cp38-win_amd64.whl", hash = "sha256:9194cb7bc21215fde6c1b1e9685d312d2aa8f65ea9736bc6311126a91c860032", size = 22712424 }, + { url = "https://files.pythonhosted.org/packages/ac/96/6a64d241498380dc5f0dca8e48981cd610d31d661a59f90d5ac242546906/scikit_image-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54df1ddc854f37a912d42bc724e456e86858107e94048a81a27720bc588f9937", size = 13011056 }, + { url = "https://files.pythonhosted.org/packages/c4/09/0b465a48f9bc7e848538f82e62811978932132b1edd50da758a9243cef5a/scikit_image-0.21.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:c01e3ab0a1fabfd8ce30686d4401b7ed36e6126c9d4d05cb94abf6bdc46f7ac9", size = 12424218 }, + { url = "https://files.pythonhosted.org/packages/2f/35/fb5f6a7d46c5dfcbb44d55cff39eb159e74752389097deee1af18a1447ce/scikit_image-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ef5d8d1099317b7b315b530348cbfa68ab8ce32459de3c074d204166951025c", size = 13313982 }, + { url = "https://files.pythonhosted.org/packages/19/bd/a53569a0a698d925eb46dbea0bd3b6b62e7287a9ec88b5a03efa8ebd5b14/scikit_image-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b1e96c59cab640ca5c5b22c501524cfaf34cbe0cb51ba73bd9a9ede3fb6e1d", size = 13828794 }, + { url = "https://files.pythonhosted.org/packages/32/b2/1811645651153407f1e715b75afe9962d87582bee70b42c8671c255f8fe6/scikit_image-0.21.0-cp39-cp39-win_amd64.whl", hash = "sha256:9cffcddd2a5594c0a06de2ae3e1e25d662745a26f94fda31520593669677c010", size = 22896233 }, +] + +[[package]] +name = "scikit-image" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "imageio", version = "2.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "lazy-loader", marker = "python_full_version == '3.9.*'" }, + { name = "networkx", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "packaging", marker = "python_full_version == '3.9.*'" }, + { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "tifffile", version = "2024.8.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/c5/bcd66bf5aae5587d3b4b69c74bee30889c46c9778e858942ce93a030e1f3/scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab", size = 22693928 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/99/89/3fcd68d034db5d29c974e964d03deec9d0fbf9410ff0a0b95efff70947f6/scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7", size = 12864601 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/50/b2/d5e97115733e2dc657e99868ae0237705b79d0c81f6ced21b8f0799a30d1/scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c", size = 12843506 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/d9/17/b561823143eb931de0f82fed03ae128ef954a9641309602ea0901c357f95/scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc", size = 12949363 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/9d/63/233300aa76c65a442a301f9d2416a9b06c91631287bd6dd3d6b620040096/scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69", size = 12891952 }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "imageio", version = "2.37.0", source = { registry = "https://pypi.org/simple" }, 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 = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scipy", version = "1.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tifffile", version = "2025.3.30", 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 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116 }, + { 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 }, +] + +[[package]] +name = "scikit-learn" +version = "0.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "scipy", version = "1.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "threadpoolctl", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "threadpoolctl", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/04/507280f20fafc8bc94b41e0592938c6f4a910d0e066be7c8ff1299628f5d/scikit-learn-0.24.2.tar.gz", hash = "sha256:d14701a12417930392cd3898e9646cf5670c190b933625ebe7511b1f7d7b8736", size = 7524030 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/6c/31c623a656ab66e938b2432cb8e41dc5497dec670ff07e1e74989ee0ab77/scikit_learn-0.24.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:90a297330f608adeb4d2e9786c6fda395d3150739deb3d42a86d9a4c2d15bc1d", size = 7198770 }, + { url = "https://files.pythonhosted.org/packages/89/ac/5760aed927165f5b1e0b7543e804f6b29b768f8992e5255fdb3495b09bde/scikit_learn-0.24.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f1d2108e770907540b5248977e4cff9ffaf0f73d0d13445ee938df06ca7579c6", size = 19350355 }, + { url = "https://files.pythonhosted.org/packages/23/87/5eb251917dc7d42119565a3a8a1937f8186399c9df8fbb1537ced97c0499/scikit_learn-0.24.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1eec963fe9ffc827442c2e9333227c4d49749a44e592f305398c1db5c1563393", size = 20429843 }, + { url = "https://files.pythonhosted.org/packages/8a/e2/7d0a3f450d3a99c1a2f3485084e437b1d0ad69d5ded89def17e644bd8e10/scikit_learn-0.24.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:2db429090b98045d71218a9ba913cc9b3fe78e0ba0b6b647d8748bc6d5a44080", size = 23704391 }, + { url = "https://files.pythonhosted.org/packages/36/dd/06b6f0ad7b86c3edc3a02d255f3fcc98e444a847cea10b3a0182251ca7b6/scikit_learn-0.24.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:62214d2954377fcf3f31ec867dd4e436df80121e7a32947a0b3244f58f45e455", size = 24885741 }, + { url = "https://files.pythonhosted.org/packages/35/ed/4bac8cc77baa335f31e1ea7dd91fc3b0793df755192aa384c7ca4dee4841/scikit_learn-0.24.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8fac72b9688176922f9f54fda1ba5f7ffd28cbeb9aad282760186e8ceba9139a", size = 26530912 }, + { url = "https://files.pythonhosted.org/packages/9a/da/c0fdac5d401c11dfb4f6d881fccd4000add616ade3b0e45e9f35c14ca001/scikit_learn-0.24.2-cp38-cp38-win32.whl", hash = "sha256:ae426e3a52842c6b6d77d00f906b6031c8c2cfdfabd6af7511bb4bc9a68d720e", size = 6142216 }, + { url = "https://files.pythonhosted.org/packages/9a/ff/5951e53ffed8d0fdb958883bd8a49b050f07c9b0c96a643607006bf690fe/scikit_learn-0.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:038f4e9d6ef10e1f3fe82addc3a14735c299866eb10f2c77c090410904828312", size = 6896277 }, + { url = "https://files.pythonhosted.org/packages/c6/6a/41fe5034d4f85c24aea9b7dca1f070bcca2ec29251d0624e87436bf5a2c0/scikit_learn-0.24.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:48f273836e19901ba2beecd919f7b352f09310ce67c762f6e53bc6b81cacf1f0", size = 7299152 }, + { url = "https://files.pythonhosted.org/packages/02/7a/1ec25170b09942fd099110ba213ec9e09b1e9e0a5aeeb5596e42a7e9dd9d/scikit_learn-0.24.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a2a47449093dcf70babc930beba2ca0423cb7df2fa5fd76be5260703d67fa574", size = 18565258 }, + { url = "https://files.pythonhosted.org/packages/13/ac/7ea73841bbed1f1c60f6e960dc9bbf1401f84926ef9db69cfce07d61127a/scikit_learn-0.24.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0e71ce9c7cbc20f6f8b860107ce15114da26e8675238b4b82b7e7cd37ca0c087", size = 19556544 }, + { url = "https://files.pythonhosted.org/packages/82/42/397ba53eab6062b8a4e2a0a7681aed66e3e7912e59628aae592376209ffd/scikit_learn-0.24.2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2754c85b2287333f9719db7f23fb7e357f436deed512db3417a02bf6f2830aa5", size = 22675859 }, + { url = "https://files.pythonhosted.org/packages/04/e2/b43d4205124dd4c1f14606b2e2d78303db993c6653a90bf11dd0ffe23b5b/scikit_learn-0.24.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7be1b88c23cfac46e06404582215a917017cd2edaa2e4d40abe6aaff5458f24b", size = 23809736 }, + { url = "https://files.pythonhosted.org/packages/bf/48/76f46db8936adf6a279daa82341f53b89cac01f40c683a3f22bc9a4362a5/scikit_learn-0.24.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:4e6198675a6f9d333774671bd536668680eea78e2e81c0b19e57224f58d17f37", size = 25544637 }, + { url = "https://files.pythonhosted.org/packages/7c/2d/80fbde8fcb68b9961afbd8c810d8451577c74cdd46a909c9e39481531127/scikit_learn-0.24.2-cp39-cp39-win32.whl", hash = "sha256:cbdb0b3db99dd1d5f69d31b4234367d55475add31df4d84a3bd690ef017b55e2", size = 6129308 }, + { url = "https://files.pythonhosted.org/packages/eb/bb/0bf5164cf4f31eb01feff6b1c3ae205ebda58aa3fc6beaaa53617185b0e6/scikit_learn-0.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:40556bea1ef26ef54bc678d00cf138a63069144a0b5f3a436eecd8f3468b903e", size = 6881380 }, +] + +[[package]] +name = "scipy" +version = "1.10.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/a9/2bf119f3f9cff1f376f924e39cfae18dec92a1514784046d185731301281/scipy-1.10.1.tar.gz", hash = "sha256:2cf9dfb80a7b4589ba4c40ce7588986d6d5cebc5457cad2c2880f6bc2d42f3a5", size = 42407997 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/ac/b1f1bbf7b01d96495f35be003b881f10f85bf6559efb6e9578da832c2140/scipy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7354fd7527a4b0377ce55f286805b34e8c54b91be865bac273f527e1b839019", size = 35093243 }, + { url = "https://files.pythonhosted.org/packages/ea/e5/452086ebed676ce4000ceb5eeeb0ee4f8c6f67c7e70fb9323a370ff95c1f/scipy-1.10.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4b3f429188c66603a1a5c549fb414e4d3bdc2a24792e061ffbd607d3d75fd84e", size = 28772969 }, + { url = "https://files.pythonhosted.org/packages/04/0b/a1b119c869b79a2ab459b7f9fd7e2dea75a9c7d432e64e915e75586bd00b/scipy-1.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1553b5dcddd64ba9a0d95355e63fe6c3fc303a8fd77c7bc91e77d61363f7433f", size = 30886961 }, + { url = "https://files.pythonhosted.org/packages/1f/4b/3bacad9a166350cb2e518cea80ab891016933cc1653f15c90279512c5fa9/scipy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c0ff64b06b10e35215abce517252b375e580a6125fd5fdf6421b98efbefb2d2", size = 34422544 }, + { url = "https://files.pythonhosted.org/packages/ec/e3/b06ac3738bf365e89710205a471abe7dceec672a51c244b469bc5d1291c7/scipy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:fae8a7b898c42dffe3f7361c40d5952b6bf32d10c4569098d276b4c547905ee1", size = 42484848 }, + { url = "https://files.pythonhosted.org/packages/e7/53/053cd3669be0d474deae8fe5f757bff4c4f480b8a410231e0631c068873d/scipy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f1564ea217e82c1bbe75ddf7285ba0709ecd503f048cb1236ae9995f64217bd", size = 35003170 }, + { url = "https://files.pythonhosted.org/packages/0d/3e/d05b9de83677195886fb79844fcca19609a538db63b1790fa373155bc3cf/scipy-1.10.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d925fa1c81b772882aa55bcc10bf88324dadb66ff85d548c71515f6689c6dac5", size = 28717513 }, + { url = "https://files.pythonhosted.org/packages/a5/3d/b69746c50e44893da57a68457da3d7e5bb75f6a37fbace3769b70d017488/scipy-1.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaea0a6be54462ec027de54fca511540980d1e9eea68b2d5c1dbfe084797be35", size = 30687257 }, + { url = "https://files.pythonhosted.org/packages/21/cd/fe2d4af234b80dc08c911ce63fdaee5badcdde3e9bcd9a68884580652ef0/scipy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15a35c4242ec5f292c3dd364a7c71a61be87a3d4ddcc693372813c0b73c9af1d", size = 34124096 }, + { url = "https://files.pythonhosted.org/packages/65/76/903324159e4a3566e518c558aeb21571d642f781d842d8dd0fd9c6b0645a/scipy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b8e0bcb877faf0abfb613d51026cd5cc78918e9530e375727bf0625c82788f", size = 42238704 }, + { url = "https://files.pythonhosted.org/packages/a0/e3/37508a11dae501349d7c16e4dd18c706a023629eedc650ee094593887a89/scipy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5678f88c68ea866ed9ebe3a989091088553ba12c6090244fdae3e467b1139c35", size = 35041063 }, + { url = "https://files.pythonhosted.org/packages/93/4a/50c436de1353cce8b66b26e49a687f10b91fe7465bf34e4565d810153003/scipy-1.10.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:39becb03541f9e58243f4197584286e339029e8908c46f7221abeea4b749fa88", size = 28797694 }, + { url = "https://files.pythonhosted.org/packages/d2/b5/ff61b79ad0ebd15d87ade10e0f4e80114dd89fac34a5efade39e99048c91/scipy-1.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bce5869c8d68cf383ce240e44c1d9ae7c06078a9396df68ce88a1230f93a30c1", size = 31024657 }, + { url = "https://files.pythonhosted.org/packages/69/f0/fb07a9548e48b687b8bf2fa81d71aba9cfc548d365046ca1c791e24db99d/scipy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07c3457ce0b3ad5124f98a86533106b643dd811dd61b548e78cf4c8786652f6f", size = 34540352 }, + { url = "https://files.pythonhosted.org/packages/32/8e/7f403535ddf826348c9b8417791e28712019962f7e90ff845896d6325d09/scipy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:049a8bbf0ad95277ffba9b3b7d23e5369cc39e66406d60422c8cfef40ccc8415", size = 42215036 }, + { url = "https://files.pythonhosted.org/packages/d9/7d/78b8035bc93c869b9f17261c87aae97a9cdb937f65f0d453c2831aa172fc/scipy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd9f1027ff30d90618914a64ca9b1a77a431159df0e2a195d8a9e8a04c78abf9", size = 35158611 }, + { url = "https://files.pythonhosted.org/packages/e7/f0/55d81813b1a4cb79ce7dc8290eac083bf38bfb36e1ada94ea13b7b1a5f79/scipy-1.10.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:79c8e5a6c6ffaf3a2262ef1be1e108a035cf4f05c14df56057b64acc5bebffb6", size = 28902591 }, + { url = "https://files.pythonhosted.org/packages/77/d1/722c457b319eed1d642e0a14c9be37eb475f0e6ed1f3401fa480d5d6d36e/scipy-1.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51af417a000d2dbe1ec6c372dfe688e041a7084da4fdd350aeb139bd3fb55353", size = 30960654 }, + { url = "https://files.pythonhosted.org/packages/5d/30/b2a2a5bf1a3beefb7609fb871dcc6aef7217c69cef19a4631b7ab5622a8a/scipy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b4735d6c28aad3cdcf52117e0e91d6b39acd4272f3f5cd9907c24ee931ad601", size = 34458863 }, + { url = "https://files.pythonhosted.org/packages/35/20/0ec6246bbb43d18650c9a7cad6602e1a84fd8f9564a9b84cc5faf1e037d0/scipy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ff7f37b1bf4417baca958d254e8e2875d0cc23aaadbe65b3d5b3077b0eb23ea", size = 42509516 }, +] + +[[package]] +name = "scipy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440 }, +] + +[[package]] +name = "scipy" +version = "1.15.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/df/ef233fff6838fe6f7840d69b5ef9f20d2b5c912a8727b21ebf876cb15d54/scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9", size = 38692502 }, + { url = "https://files.pythonhosted.org/packages/5c/20/acdd4efb8a68b842968f7bc5611b1aeb819794508771ad104de418701422/scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5", size = 30085508 }, + { url = "https://files.pythonhosted.org/packages/42/55/39cf96ca7126f1e78ee72a6344ebdc6702fc47d037319ad93221063e6cf4/scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e", size = 22359166 }, + { url = "https://files.pythonhosted.org/packages/51/48/708d26a4ab8a1441536bf2dfcad1df0ca14a69f010fba3ccbdfc02df7185/scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9", size = 25112047 }, + { url = "https://files.pythonhosted.org/packages/dd/65/f9c5755b995ad892020381b8ae11f16d18616208e388621dfacc11df6de6/scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3", size = 35536214 }, + { url = "https://files.pythonhosted.org/packages/de/3c/c96d904b9892beec978562f64d8cc43f9cca0842e65bd3cd1b7f7389b0ba/scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d", size = 37646981 }, + { url = "https://files.pythonhosted.org/packages/3d/74/c2d8a24d18acdeae69ed02e132b9bc1bb67b7bee90feee1afe05a68f9d67/scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58", size = 37230048 }, + { url = "https://files.pythonhosted.org/packages/42/19/0aa4ce80eca82d487987eff0bc754f014dec10d20de2f66754fa4ea70204/scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa", size = 40010322 }, + { url = "https://files.pythonhosted.org/packages/d0/d2/f0683b7e992be44d1475cc144d1f1eeae63c73a14f862974b4db64af635e/scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65", size = 41233385 }, + { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 }, + { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 }, + { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 }, + { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523 }, + { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 }, + { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 }, + { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 }, + { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 }, + { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 }, + { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 }, + { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 }, + { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 }, + { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 }, + { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 }, + { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 }, + { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 }, + { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 }, + { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 }, + { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587 }, + { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266 }, + { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768 }, + { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719 }, + { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 }, + { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 }, + { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 }, + { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 }, + { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286 }, + { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634 }, + { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179 }, + { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412 }, + { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 }, + { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 }, + { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 }, + { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 }, + { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, +] + +[[package]] +name = "setuptools" +version = "58.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/00/05f51ceab8d3b9be4295000d8be4c830c53e5477755888994e9825606cd9/setuptools-58.5.3.tar.gz", hash = "sha256:dae6b934a965c8a59d6d230d3867ec408bb95e73bd538ff77e71fedf1eaca729", size = 2269854 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/e9/84e2865fddfaba4506bc5d293d2a535bf27e31b12ca16d31564f8ce28cdb/setuptools-58.5.3-py3-none-any.whl", hash = "sha256:a481fbc56b33f5d8f6b33dce41482e64c68b668be44ff42922903b03872590bf", size = 946828 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "sphinx" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster", version = "0.7.13", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "importlib-metadata", version = "8.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp", version = "1.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "sphinxcontrib-applehelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "sphinxcontrib-devhelp", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "sphinxcontrib-devhelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "sphinxcontrib-htmlhelp", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "sphinxcontrib-htmlhelp", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "sphinxcontrib-qthelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "sphinxcontrib-serializinghtml", version = "1.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "sphinxcontrib-serializinghtml", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/b9/b831ea20dde3c3b726e41403eaee92cc448083cef310790c31c6ccfb22e3/Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6", size = 6698212 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/96/9cbbc7103fb482d5809fe4976ecb9b627058210d02817fcbfeebeaa8f762/Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226", size = 3099508 }, +] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343 }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/3e/477c5b3ed78b6818d673f63512db12ace8c89e83eb9eecc913f9e2cc8416/sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931", size = 2785069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/01/76f40a18e9209bb098c1c1313c823dbbd001b23a2db71e7fd4eb5a48559c/sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0", size = 2824803 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/32/df/45e827f4d7e7fcc84e853bcef1d836effd762d63ccb86f43ede4e98b478c/sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e", size = 24766 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/c1/5e2cafbd03105ce50d8500f9b4e8a6e8d02e22d0475b574c3b3e9451a15f/sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228", size = 120601 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-bibtex" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "importlib-metadata", version = "8.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "pybtex" }, + { name = "pybtex-docutils" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/ce/054a8ec04063f9a27772fea7188f796edbfa382e656d3b76428323861f0e/sphinxcontrib_bibtex-2.6.3.tar.gz", hash = "sha256:7c790347ef1cb0edf30de55fc324d9782d085e89c52c2b8faafa082e08e23946", size = 117177 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/49/c23f9493c0a5d5881fb7ed3002e87708454fef860aa96a48e755d27bf6f0/sphinxcontrib_bibtex-2.6.3-py3-none-any.whl", hash = "sha256:ff016b738fcc867df0f75c29e139b3b2158d26a2c802db27963cb128be3b75fb", size = 40340 }, +] + +[[package]] +name = "sphinxcontrib-datatemplates" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, + { name = "pyyaml" }, + { name = "sphinx" }, + { name = "sphinxcontrib-runcmd" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/9e/8ac54a6a3e7a00339f417568899b64a0c0d622429db73cc1a28c8122c8e2/sphinxcontrib.datatemplates-0.11.0.tar.gz", hash = "sha256:793222e803430076341509cc167f8d715830b05e418c885313101d60fd442557", size = 30996 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/8d/7a7dd95ad1eedec8dc770570c8b1f3dc1d13357383635607b6629ccf329c/sphinxcontrib.datatemplates-0.11.0-py3-none-any.whl", hash = "sha256:88d02f5edab32b88211ebb72a90553e3676a5737877bad1de412f84058ac282e", size = 12493 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/98/33/dc28393f16385f722c893cb55539c641c9aaec8d1bc1c15b69ce0ac2dbb3/sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4", size = 17398 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", size = 84690 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/47/64cff68ea3aa450c373301e5bebfbb9fce0a3e70aca245fcadd4af06cd75/sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff", size = 27967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/ee/a1f5e39046cbb5f8bc8fba87d1ddf1c6643fbc9194e58d26e606de4b9074/sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903", size = 99833 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104 }, +] + +[[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 } +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 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/8e/c4846e59f38a5f2b4a0e3b27af38f2fcf904d4bfd82095bf92de0b114ebd/sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", size = 21658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/14/05f9206cf4e9cfca1afb5fd224c7cd434dcc3a433d6d9e4e0264d29c6cdb/sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6", size = 90609 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-runcmd" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/03/6eb30814c9839f36131284a46ec9fc39d7bd356078648bc7125d5d1c05e8/sphinxcontrib-runcmd-0.2.0.tar.gz", hash = "sha256:3551c389d9c5fe82d693c7222feb9658b1a1a5a1abcb0063e8385e5528c64c76", size = 5376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/d9/67a79080b5d9fcb367470af9e525a9c53122e95744665de09462dcd676d8/sphinxcontrib_runcmd-0.2.0-py2.py3-none-any.whl", hash = "sha256:7b739b68e27210b4c7c12ba16e5b3da7b313c49991401f896d29bea0f0771934", size = 6043 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/72/835d6fadb9e5d02304cf39b18f93d227cd93abd3c41ebf58e6853eeb1455/sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952", size = 21019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/77/5464ec50dd0f1c1037e3c93249b040c8fc8078fdda97530eeb02424b6eea/sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", size = 94021 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, +] + +[[package]] +name = "tifffile" +version = "2023.7.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/22/4d19feaba862f06f6392499d5617f96b0d8eb9a876e33e9b6aab292b88f2/tifffile-2023.7.10.tar.gz", hash = "sha256:c06ec460926d16796eeee249a560bcdddf243daae36ac62af3c84a953cd60b4a", size = 345689 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/a3/68d17088a4f09565bc7341fd20490da8191ec4cddde479daaabbe07bb603/tifffile-2023.7.10-py3-none-any.whl", hash = "sha256:94dfdec321ace96abbfe872a66cfd824800c099a2db558443453eebc2c11b304", size = 220889 }, +] + +[[package]] +name = "tifffile" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/30/7017e5560154c100cad3a801c02adb48879cd8e8cb862b82696d84187184/tifffile-2024.8.30.tar.gz", hash = "sha256:2c9508fe768962e30f87def61819183fb07692c258cb175b3c114828368485a4", size = 365714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/4f/73714b1c1d339b1545cac28764e39f88c69468b5e10e51f327f9aa9d55b9/tifffile-2024.8.30-py3-none-any.whl", hash = "sha256:8bc59a8f02a2665cd50a910ec64961c5373bee0b8850ec89d3b7b485bf7be7ad", size = 227262 }, +] + +[[package]] +name = "tifffile" +version = "2025.3.30" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/54/d5ebe66a9de349b833e570e87bdbd9eec76ec54bd505c24b0591a15783ad/tifffile-2025.3.30.tar.gz", hash = "sha256:3cdee47fe06cd75367c16bc3ff34523713156dae6cd498e3a392e5b39a51b789", size = 366039 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/be/10d23cfd4078fbec6aba768a357eff9e70c0b6d2a07398425985c524ad2a/tifffile-2025.3.30-py3-none-any.whl", hash = "sha256:0ed6eee7b66771db2d1bfc42262a51b01887505d35539daef118f4ff8c0f629c", size = 226837 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, +] + +[[package]] +name = "tox" +version = "4.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock", version = "3.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "filelock", version = "3.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "packaging" }, + { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "platformdirs", version = "4.3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pluggy" }, + { name = "pyproject-api", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pyproject-api", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/87/692478f0a194f1cad64803692642bd88c12c5b64eee16bf178e4a32e979c/tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52", size = 196255 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/38/33348de6fc4b1afb3d76d8485c8aecbdabcfb3af8da53d40c792332e2b37/tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c", size = 172420 }, +] + +[[package]] +name = "tox-uv" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "importlib-resources", version = "6.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "packaging", marker = "python_full_version < '3.9'" }, + { name = "tox", marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", marker = "python_full_version < '3.9'" }, + { name = "uv", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/93/1f06c3cbfd4c1aa23859d49a76c7e65b51e60715bc22b2dd16cbff9c1e71/tox_uv-1.13.1.tar.gz", hash = "sha256:a8504b8db4bf6c81cba7cd3518851a3f1e0f6991d22272a4cc08ebe1b7f38cca", size = 15645 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/8e/94afb25547f5e4987801e8f6aa11e357190f72f31eb363267a3cb2fa6a88/tox_uv-1.13.1-py3-none-any.whl", hash = "sha256:b163dd28ca37a9f4c6d8cbac11153be27c2e929b58bcae62e323ffa8f71c327d", size = 13383 }, +] + +[[package]] +name = "tox-uv" +version = "1.25.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version >= '3.9'" }, + { name = "tox", marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions", marker = "python_full_version == '3.9.*'" }, + { name = "uv", marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/3a/3e445f25978a716ba6674f33f687d9336d0312086a277a778a5e9e9220d7/tox_uv-1.25.0.tar.gz", hash = "sha256:59ee5e694c41fef7bbcf058f22a5f9b6a8509698def2ea60c08554f4e36b9fcc", size = 21114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a7/f5c29e0e6faaccefcab607f672b176927144e9412c8183d21301ea2a6f6c/tox_uv-1.25.0-py3-none-any.whl", hash = "sha256:50cfe7795dcd49b2160d7d65b5ece8717f38cfedc242c852a40ec0a71e159bf7", size = 16431 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] + +[[package]] +name = "uv" +version = "0.6.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/eb/07bc000a3c05372448b63c45da98630c532ec4e059d848488c3e774d017a/uv-0.6.14.tar.gz", hash = "sha256:a117466f307d164a74444949cc94ec4328ec880fb489cbaa7df324dab14c5c98", size = 3134567 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/bf/3e87dec7728b249458967f39a301376cb776e559c90261c1dac963686dc3/uv-0.6.14-py3-none-linux_armv6l.whl", hash = "sha256:c775e5d7a80ff43cb88856bbdcd838918d5ac3dc362414317e6bbaeb615fff98", size = 16228143 }, + { url = "https://files.pythonhosted.org/packages/24/b2/111e1ea40453d93c849f36a67397b51d9b458e6e598c3629ffe76d11b490/uv-0.6.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2578f6f8cdbcc036ffad1043f9f66ade3ac0babf29def6abd9eefd4a7c6621cb", size = 16273279 }, + { url = "https://files.pythonhosted.org/packages/72/89/e7fc8a047f08234cc26d1e37e5f573887744205d087f8e8e6f3d0feb04ce/uv-0.6.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9fc8fe58871b4fe02a863b05b8b1b25ef1b6c60d4d224e85338f5c2be0ab4f0e", size = 15115451 }, + { url = "https://files.pythonhosted.org/packages/20/1e/72ac3d1e0805d3b49b0a4de46483489ea1989827440f42b0cfb444cdc67f/uv-0.6.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2fb2cd7f6aae21b81474b0051d30e7ed939a9a71714948c47f58b0e7acdd2a80", size = 15540456 }, + { url = "https://files.pythonhosted.org/packages/fd/47/5aeb7fb80c673bc28ccf3ab99e376b1cd92eac41af6b9b48c0e38b114c54/uv-0.6.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d6ca3f99c1a6c1c430ae8f451133fb4e8c3a22f661c257425402a5d9430bb797", size = 15979820 }, + { url = "https://files.pythonhosted.org/packages/1f/44/c3ad856473f2ef5f22c865a73a0a37ee82d11fcca78ae82f5ac895a7023a/uv-0.6.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed41877b679e0a1af9ab65427d829b87a81b499017e59c70756d4ba02ca43fcb", size = 16650494 }, + { url = "https://files.pythonhosted.org/packages/7a/f6/8a1245530c282d470909db78cf56831693c58b90d9b819e35aa2d85fbbe8/uv-0.6.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe9b4361b1c8055301b715fdd94d94eb512053dc4545fec40d3fe3657f655987", size = 17505028 }, + { url = "https://files.pythonhosted.org/packages/a5/70/0806268440651e2ad1b3542af42b800e20bb7e43050a9ca78f3d1eb4c660/uv-0.6.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998b67bb1cebbe044fc2c5cb251c29cffc56f62a6d55719d6f4e960461d6edad", size = 17245854 }, + { url = "https://files.pythonhosted.org/packages/2a/3a/0da9780868626466d8c4977fb02d1b0daa80e6f7504d7b662cae3fb4af3d/uv-0.6.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d433925db6e2ef46047b68962d136ff2ef17a7b5609168615f19e60674232c9", size = 21584756 }, + { url = "https://files.pythonhosted.org/packages/eb/fd/21a82b78173be1a2ea20f4f55154e7252bd80d21ed60b9bbbc0e2047b8d0/uv-0.6.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36aaeb00a70a10f748e16c7a1fc410862e2ba905806e7e9dfbc3e64596309404", size = 16878847 }, + { url = "https://files.pythonhosted.org/packages/6c/9a/7c84650ae9fb801ecc848d49dcba201243989d9234fe3ec4a4e935ff21c0/uv-0.6.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:11779beb3bd1f92814bc8d8cd350d5228e8f9198cca2f52138b53030a4061d93", size = 15810089 }, + { url = "https://files.pythonhosted.org/packages/0b/b3/efcbd3a2d298801109b24feee655bb80fe4178aa6bf68e49664c48b342b2/uv-0.6.14-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:bf1ec103cf9a0850f03935dc6a93cacc680fa2c90c3b41cfc10da311afab8f5b", size = 15962056 }, + { url = "https://files.pythonhosted.org/packages/3f/53/c92c894cb34e9578c2e6dc195bcd4eb0a140dd57c96a60207d847521a902/uv-0.6.14-py3-none-musllinux_1_1_i686.whl", hash = "sha256:955e36c98a438a249e178988d4f13b1bb831eb57264d73c459f171b5afd7b023", size = 16255226 }, + { url = "https://files.pythonhosted.org/packages/df/eb/38bc37856691d53008bf094d03d9e7ab0c2927523a3901c83e152e7c9915/uv-0.6.14-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2d534e7dc1299c8b53eb7b4c7575e4f0933673ea8b1275d3f3022f5670e311db", size = 17005225 }, + { url = "https://files.pythonhosted.org/packages/d8/fe/087d5193603e16bc5f67556d94cf8fa8634785c5863cccdec825f14e9a4c/uv-0.6.14-py3-none-win32.whl", hash = "sha256:7cdf3c8d927b07d4eaffc44809eb57523d449705f10dabbdd6f34f7bdfc7d5fe", size = 16131231 }, + { url = "https://files.pythonhosted.org/packages/40/17/33c5c1503c35c874932d4a21ec10a55051e3695dba12b7de700bcfad0cca/uv-0.6.14-py3-none-win_amd64.whl", hash = "sha256:012f46bef6909209c4a6749e4019eb755ba762d37d7ceaaf76da9cb4b7f771e9", size = 17628508 }, + { url = "https://files.pythonhosted.org/packages/77/09/163062d439ddc0d89e527ae0e631abf1f7781b183442d8823c48af368f5d/uv-0.6.14-py3-none-win_arm64.whl", hash = "sha256:7465081b4d0b213d0055ccb48de7fe546b5cf0853c6d3601115760760634f6d8", size = 16387232 }, +] + +[[package]] +name = "virtualenv" +version = "20.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock", version = "3.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "filelock", version = "3.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "platformdirs", version = "4.3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 }, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307 }, + { url = "https://files.pythonhosted.org/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486 }, + { url = "https://files.pythonhosted.org/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777 }, + { url = "https://files.pythonhosted.org/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314 }, + { url = "https://files.pythonhosted.org/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947 }, + { url = "https://files.pythonhosted.org/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778 }, + { url = "https://files.pythonhosted.org/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716 }, + { url = "https://files.pythonhosted.org/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548 }, + { url = "https://files.pythonhosted.org/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334 }, + { url = "https://files.pythonhosted.org/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427 }, + { url = "https://files.pythonhosted.org/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774 }, + { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308 }, + { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488 }, + { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776 }, + { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776 }, + { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420 }, + { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199 }, + { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307 }, + { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025 }, + { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879 }, + { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419 }, + { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773 }, + { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799 }, + { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821 }, + { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919 }, + { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721 }, + { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899 }, + { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222 }, + { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707 }, + { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685 }, + { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567 }, + { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672 }, + { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865 }, + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800 }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824 }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920 }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690 }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861 }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174 }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721 }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763 }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585 }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676 }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871 }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312 }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062 }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155 }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471 }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208 }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339 }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232 }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476 }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377 }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986 }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750 }, + { url = "https://files.pythonhosted.org/packages/0c/66/95b9e90e6e1274999b183c9c3f984996d870e933ca9560115bd1cd1d6f77/wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9", size = 53234 }, + { url = "https://files.pythonhosted.org/packages/a4/b6/6eced5e2db5924bf6d9223d2bb96b62e00395aae77058e6a9e11bf16b3bd/wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119", size = 38462 }, + { url = "https://files.pythonhosted.org/packages/5d/a4/c8472fe2568978b5532df84273c53ddf713f689d408a4335717ab89547e0/wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6", size = 38730 }, + { url = "https://files.pythonhosted.org/packages/3c/70/1d259c6b1ad164eb23ff70e3e452dd1950f96e6473f72b7207891d0fd1f0/wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9", size = 86225 }, + { url = "https://files.pythonhosted.org/packages/a9/68/6b83367e1afb8de91cbea4ef8e85b58acdf62f034f05d78c7b82afaa23d8/wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a", size = 78055 }, + { url = "https://files.pythonhosted.org/packages/0d/21/09573d2443916705c57fdab85d508f592c0a58d57becc53e15755d67fba2/wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2", size = 85592 }, + { url = "https://files.pythonhosted.org/packages/45/ce/700e17a852dd5dec894e241c72973ea82363486bcc1fb05d47b4fbd1d683/wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a", size = 83906 }, + { url = "https://files.pythonhosted.org/packages/37/14/bd210faf0a66faeb8529d42b6b45a25d6aa6ce25ddfc19168e4161aed227/wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04", size = 76763 }, + { url = "https://files.pythonhosted.org/packages/34/0c/85af70d291f44659c422416f0272046109e785bf6db8c081cfeeae5715c5/wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f", size = 83573 }, + { url = "https://files.pythonhosted.org/packages/f8/1e/b215068e824878f69ea945804fa26c176f7c2735a3ad5367d78930bd076a/wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7", size = 36408 }, + { url = "https://files.pythonhosted.org/packages/52/27/3dd9ad5f1097b33c95d05929e409cc86d7c765cb5437b86694dc8f8e9af0/wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3", size = 38737 }, + { url = "https://files.pythonhosted.org/packages/8a/f4/6ed2b8f6f1c832933283974839b88ec7c983fd12905e01e97889dadf7559/wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a", size = 53308 }, + { url = "https://files.pythonhosted.org/packages/a2/a9/712a53f8f4f4545768ac532619f6e56d5d0364a87b2212531685e89aeef8/wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061", size = 38489 }, + { url = "https://files.pythonhosted.org/packages/fa/9b/e172c8f28a489a2888df18f953e2f6cb8d33b1a2e78c9dfc52d8bf6a5ead/wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82", size = 38776 }, + { url = "https://files.pythonhosted.org/packages/cf/cb/7a07b51762dcd59bdbe07aa97f87b3169766cadf240f48d1cbe70a1be9db/wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9", size = 83050 }, + { url = "https://files.pythonhosted.org/packages/a5/51/a42757dd41032afd6d8037617aa3bc6803ba971850733b24dfb7d5c627c4/wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f", size = 74718 }, + { url = "https://files.pythonhosted.org/packages/bf/bb/d552bfe47db02fcfc950fc563073a33500f8108efa5f7b41db2f83a59028/wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b", size = 82590 }, + { url = "https://files.pythonhosted.org/packages/77/99/77b06b3c3c410dbae411105bf22496facf03a5496bfaca8fbcf9da381889/wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f", size = 81462 }, + { url = "https://files.pythonhosted.org/packages/2d/21/cf0bd85ae66f92600829ea1de8e1da778e5e9f6e574ccbe74b66db0d95db/wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8", size = 74309 }, + { url = "https://files.pythonhosted.org/packages/6d/16/112d25e9092398a0dd6fec50ab7ac1b775a0c19b428f049785096067ada9/wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9", size = 81081 }, + { url = "https://files.pythonhosted.org/packages/2b/49/364a615a0cc0872685646c495c7172e4fc7bf1959e3b12a1807a03014e05/wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb", size = 36423 }, + { url = "https://files.pythonhosted.org/packages/00/ad/5d2c1b34ba3202cd833d9221833e74d6500ce66730974993a8dc9a94fb8c/wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb", size = 38772 }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, +] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200 }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, +] diff --git a/tests/linters/.flake8 b/tests/linters/.flake8 new file mode 100644 index 0000000..10b985e --- /dev/null +++ b/tests/linters/.flake8 @@ -0,0 +1,16 @@ + +# Flake8 configuration file +[flake8] + +# Ignores the following rules: +# R0902 Too many instance attributes +# R0913 Too many arguments +# R0914 Too many local variables +# W503 Line-break before binary operator +ignore = R0902,R0913,R0914,W503 + +# Exclude the following directories from linting +exclude=.venv,.git,.tox,build,dist,docs,*egg,*.ini + +# Maximum number of characters on a single line +max-line-length = 120 diff --git a/pylintrc b/tests/linters/.pylintrc similarity index 100% rename from pylintrc rename to tests/linters/.pylintrc diff --git a/tests/cspell/.cspell.json b/tests/linters/cspell/.cspell.json similarity index 99% rename from tests/cspell/.cspell.json rename to tests/linters/cspell/.cspell.json index 5a547a7..a0dffba 100644 --- a/tests/cspell/.cspell.json +++ b/tests/linters/cspell/.cspell.json @@ -105,6 +105,7 @@ "doctree", "docz", "dtypes", + "dvipng", "edcba", "eigenstuff", "eigsh", @@ -194,6 +195,7 @@ "symmetrix", "tamasfe", "testpaths", + "texlive", "toctree", "todense", "topmodulename", diff --git a/tests/cspell/package-lock.json b/tests/linters/cspell/package-lock.json similarity index 100% rename from tests/cspell/package-lock.json rename to tests/linters/cspell/package-lock.json diff --git a/tests/cspell/package.json b/tests/linters/cspell/package.json similarity index 76% rename from tests/cspell/package.json rename to tests/linters/cspell/package.json index ea771f1..fb8d91c 100644 --- a/tests/cspell/package.json +++ b/tests/linters/cspell/package.json @@ -12,6 +12,6 @@ "cspell": "^8.16.1" }, "scripts": { - "cspell": "npx --prefix tests/cspell cspell lint --config .cspell.json --root ../.. '**'" + "cspell": "npx --prefix tests/linters/cspell cspell lint --config .cspell.json --root ../.. '**'" } } diff --git a/tests/tox.ini b/tests/tox.ini new file mode 100644 index 0000000..e62207d --- /dev/null +++ b/tests/tox.ini @@ -0,0 +1,98 @@ + +# Core tox configuration +[tox] + +# A list of environments that will be run by default when running tox without specifying any environment +envlist = py38,py39,coverage,pylint,flake8,docs + +# Base configuration for test environments that tox will fallback to for missing values; this avoids having to repeat the same configuration for the +# unit test environments py38 and py39 over and over again +[testenv] +setenv = + COVERAGE_FILE = .coverage.{envname} +commands = + uv run pytest \ + --cov "../source/corelay" \ + --cov-config "tox.ini" \ + --cov-append \ + {posargs:unit_tests} + +# A test environment that combines the coverage data from all runs of the unit tests with the different Python versions and generates a single report +[testenv:coverage] +setenv = + COVERAGE_FILE = /.coverage +depends = py38,py39 +commands = + uv run coverage combine + uv run coverage report -m + +# A test environment that will build the documentation using Sphinx +[testenv:docs] +basepython = python3.9.21 +commands = + uv run sphinx-build \ + --color \ + -W \ + --keep-going \ + -d "../docs/doctree" \ + -b html \ + "../docs/source" \ + "../docs/build" \ + {posargs} + +# A test environment that will run the Flake8 meta-linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples +[testenv:flake8] +basepython = python3.9.21 +commands = + uv run flake8 \ + --config "linters/.flake8" \ + "../source/corelay" \ + "unit_tests" \ + "../docs/source/conf.py" \ + "../example" + {posargs} + +# A test environment that will run the PyLint linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples +[testenv:pylint] +basepython = python3.9.21 +commands = + uv run pylint \ + --rcfile="linters/.pylintrc" \ + --output-format="parseable" \ + "../source/corelay" \ + "unit_tests" \ + "../docs/source/conf.py" \ + "../example" + +# The configuration for the PyTest test runner +[pytest] +testpaths = unit_tests +addopts = -ra -l +filterwarnings = error + +# The general configuration for the PyTest coverage plugin +[coverage:run] + +# Specifies that branch coverage should be measured +branch = true + +# Causes the PyTest coverage plugin to append the machine name, process ID, and a random number to the data file name to simplify collecting data from +# many processes; this is done because we are running the unit tests using multiple Python versions +parallel = true + +# The configuration for the PyTest coverage plugin when generating the coverage report +[coverage:report] + +# Specifies that files that have 100% coverage should be omitted from the report, so that we can focus on the files that need more testing +skip_covered = true + +# Specifies that the report should include a list of the lines that were not covered by the unit tests +show_missing = true + +# Since the coverage data can be collected from multiple different installations of CoRelAy, the Coverage tool needs to know which files are +# equivalent; this configuration section contains named lists (in our case only a single list called "source"), where two file paths are considered +# equivalent and combined when running the "coverage combine" command when they are in the same list; here we specify that the files in directories +# called */source/corelay and */.tox/*/lib/python*/site-packages/corelay are equivalent +[coverage:paths] +source = */source/corelay,*/.tox/*/lib/python*/site-packages/corelay + diff --git a/tests/__init__.py b/tests/unit_tests/__init__.py similarity index 100% rename from tests/__init__.py rename to tests/unit_tests/__init__.py diff --git a/tests/corelay/__init__.py b/tests/unit_tests/corelay/__init__.py similarity index 100% rename from tests/corelay/__init__.py rename to tests/unit_tests/corelay/__init__.py diff --git a/tests/corelay/io/__init__.py b/tests/unit_tests/corelay/io/__init__.py similarity index 100% rename from tests/corelay/io/__init__.py rename to tests/unit_tests/corelay/io/__init__.py diff --git a/tests/corelay/io/test_storage.py b/tests/unit_tests/corelay/io/test_storage.py similarity index 100% rename from tests/corelay/io/test_storage.py rename to tests/unit_tests/corelay/io/test_storage.py diff --git a/tests/corelay/pipeline/__init__.py b/tests/unit_tests/corelay/pipeline/__init__.py similarity index 100% rename from tests/corelay/pipeline/__init__.py rename to tests/unit_tests/corelay/pipeline/__init__.py diff --git a/tests/corelay/pipeline/test_base.py b/tests/unit_tests/corelay/pipeline/test_base.py similarity index 100% rename from tests/corelay/pipeline/test_base.py rename to tests/unit_tests/corelay/pipeline/test_base.py diff --git a/tests/corelay/pipeline/test_spectral.py b/tests/unit_tests/corelay/pipeline/test_spectral.py similarity index 100% rename from tests/corelay/pipeline/test_spectral.py rename to tests/unit_tests/corelay/pipeline/test_spectral.py diff --git a/tests/corelay/processor/__init__.py b/tests/unit_tests/corelay/processor/__init__.py similarity index 100% rename from tests/corelay/processor/__init__.py rename to tests/unit_tests/corelay/processor/__init__.py diff --git a/tests/corelay/processor/test_base.py b/tests/unit_tests/corelay/processor/test_base.py similarity index 100% rename from tests/corelay/processor/test_base.py rename to tests/unit_tests/corelay/processor/test_base.py diff --git a/tests/corelay/processor/test_clustering.py b/tests/unit_tests/corelay/processor/test_clustering.py similarity index 100% rename from tests/corelay/processor/test_clustering.py rename to tests/unit_tests/corelay/processor/test_clustering.py diff --git a/tests/corelay/processor/test_embedding.py b/tests/unit_tests/corelay/processor/test_embedding.py similarity index 100% rename from tests/corelay/processor/test_embedding.py rename to tests/unit_tests/corelay/processor/test_embedding.py diff --git a/tests/corelay/processor/test_flow.py b/tests/unit_tests/corelay/processor/test_flow.py similarity index 100% rename from tests/corelay/processor/test_flow.py rename to tests/unit_tests/corelay/processor/test_flow.py diff --git a/tests/corelay/processor/test_preprocessing.py b/tests/unit_tests/corelay/processor/test_preprocessing.py similarity index 100% rename from tests/corelay/processor/test_preprocessing.py rename to tests/unit_tests/corelay/processor/test_preprocessing.py diff --git a/tests/corelay/test_base.py b/tests/unit_tests/corelay/test_base.py similarity index 100% rename from tests/corelay/test_base.py rename to tests/unit_tests/corelay/test_base.py diff --git a/tests/corelay/test_plugboard.py b/tests/unit_tests/corelay/test_plugboard.py similarity index 100% rename from tests/corelay/test_plugboard.py rename to tests/unit_tests/corelay/test_plugboard.py diff --git a/tests/corelay/test_tracker.py b/tests/unit_tests/corelay/test_tracker.py similarity index 100% rename from tests/corelay/test_tracker.py rename to tests/unit_tests/corelay/test_tracker.py diff --git a/tests/corelay/test_utils.py b/tests/unit_tests/corelay/test_utils.py similarity index 100% rename from tests/corelay/test_utils.py rename to tests/unit_tests/corelay/test_utils.py diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 0bd3e8f..0000000 --- a/tox.ini +++ /dev/null @@ -1,84 +0,0 @@ -[tox] -skip_missing_interpreters = true -envlist = py37,py38,py39,pylint,flake8,docs - -[testenv] -extras = tests -setenv = - COVERAGE_FILE = {toxworkdir}/.coverage.{envname} -commands = - pytest \ - --cov "{envsitepackagesdir}/corelay" \ - --cov-config "{toxinidir}/tox.ini" \ - {posargs:.} - -[testenv:coverage] -deps = - coverage -setenv = - COVERAGE_FILE = {toxworkdir}/.coverage -skip_install = true -commands = - coverage combine - coverage report -m -depends = py37,py38,py39 - -[testenv:docs] -basepython = python3.9 -extras = docs -commands = - sphinx-build \ - --color \ - -W \ - --keep-going \ - -d "{toxinidir}/docs/doctree" \ - -b html \ - "{toxinidir}/docs/source" \ - "{toxinidir}/docs/build" \ - {posargs} - -[testenv:flake8] -basepython = python3.9 -changedir = {toxinidir} -deps = - flake8 -commands = - flake8 "{toxinidir}/src/corelay" "{toxinidir}/tests" {posargs} - - -[testenv:pylint] -basepython = python3.9 -deps = - pylint - pytest -changedir = {toxinidir} -commands = - pylint --rcfile=pylintrc --output-format=parseable {toxinidir}/src/corelay {toxinidir}/tests - -[flake8] -# R0902 Too many instance attributes -# R0913 Too many arguments -# R0914 Too many local variables -# W503 Line-break before binary operator -ignore = R0902,R0913,R0914,W503 - -exclude=.venv,.git,.tox,build,dist,docs,*egg,*.ini - -max-line-length = 120 - -[pytest] -testpaths = tests -addopts = -ra -l - -[coverage:run] -parallel = true -branch = true - -[coverage:report] -skip_covered = true -show_missing = true - -[coverage:paths] -source = src/corelay - */.tox/*/lib/python*/site-packages/corelay - */src/corelay From 501cbab19315a875bbb80f45edf293c142ae3951 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Thu, 17 Apr 2025 17:46:35 +0200 Subject: [PATCH 08/19] Updated the Dependencies of the Project Updated the Python dependencies in the "pyproject.toml" to their respective latest versions. Some of the dependencies no longer support Python 3.8 and Python 3.9 (as well as Python 3.10). For this reason, the project was migrated to support Python 3.11, 3.12, and 3.13. The ".python-versions" file, the tox configuration, and the GitHub Actions workflow were updated to reflect this change. One of the dependencies ("metrohash-python") requires a C++ compiler to build and uses the "c++" command, which may not be available on all systems. To ensure that users are not confused by this, a note was added to the README file explaining that the "c++" command is required to build the project and showing how to install it on Fedora (one of the systems that do not have the "c++" command installed by default).) The configuration for Read the Docs was updated to use the latest available versions of Ubuntu (24.04) and Python (3.12). It was also documented. Furthermore, the following changes were made: - The `corelay/version.py` file was deleted as it is automatically generated during the build process and should not be checked into source control. - The configuration for the GitLab CI, which was stored in the ".gitlab-ci.yml" file, was removed. The project is no longer being hosted on GitLab, and the CI configuration is no longer needed. --- .github/workflows/tests.yml | 20 +- .gitignore | 7 +- .gitlab-ci.yml | 20 - .readthedocs.yaml | 11 +- CHANGELOG.md | 6 + README.md | 7 + source/.python-versions | 5 +- source/corelay/version.py | 21 - source/pyproject.toml | 65 +- source/uv.lock | 2203 ++++------------------------------- tests/tox.ini | 13 +- 11 files changed, 311 insertions(+), 2067 deletions(-) delete mode 100644 .gitlab-ci.yml delete mode 100644 source/corelay/version.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6463eb5..259de44 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,20 +16,22 @@ on: # This workflow contains multiple jobs for unit testing, linting, type checking, spell checking, and building jobs: - # Runs the unit tests on Python 3.8 and 3.9 + # Runs the unit tests on Python 3.11, 3.12, and 3.13 unit-tests: - # The job will run on the latest version of Ubuntu using a matrix strategy, where the unit tests are run using Python 3.10, 3.11, 3.12, and 3.13 + # The job will run on the latest version of Ubuntu using a matrix strategy, where the unit tests are run using Python 3.11, 3.12, and 3.13 name: Unit Test CoRelAy on Python ${{matrix.python-version}} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - - tox-environment: py38 - python-version: "3.8.20" - - tox-environment: py39 - python-version: "3.9.21" + - tox-environment: py311 + python-version: "3.11.12" + - tox-environment: py312 + python-version: "3.12.10" + - tox-environment: py313 + python-version: "3.13.3" # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and finally 5) running the unit tests @@ -67,7 +69,7 @@ jobs: with: version: 0.6.14 - name: Install Python - run: uv python install 3.13.2 3.9.21 + run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies run: uv --directory source sync --all-extras --dev - name: Run PyLint Linter @@ -91,7 +93,7 @@ jobs: with: version: 0.6.14 - name: Install Python - run: uv python install 3.13.2 3.9.21 + run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies run: uv --directory source sync --all-extras --dev - name: Run Flake8 Linter @@ -143,7 +145,7 @@ jobs: with: version: 0.6.14 - name: Install Python - run: uv python install 3.13.2 3.9.21 + run: uv python install 3.13.3 - name: Install TeX Live for Pybtex run: sudo apt-get update -y && sudo apt-get install -y texlive texlive-latex-extra dvipng - name: Build Documentation diff --git a/.gitignore b/.gitignore index b67d2d2..acc838d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,12 +15,9 @@ Thumbs.db .*venv/ # Build output -__pycache__ -.python -*.egg-info __pycache__/ -build/ -dist/ +source/dist/ +source/corelay/version.py # MyPy type information cache .mypy_cache diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 9dc8015..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,20 +0,0 @@ - -stages: - - linting - - unit-tests - -pylint: - stage: linting - script: - - python3.7 -m tox -e pylint - -flake8: - stage: linting - script: - - python3.7 -m tox -e flake8 - -pytest: - stage: unit-tests - when: always - script: - - python3.7 -m tox -e py37 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6b20cb9..2b71c6a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,13 +1,20 @@ + +# Use the v2 version of the Read the Docs build configuration file format version: 2 +# Configures the build environment to use Ubuntu 24.04 and Python 3.12 build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.9" + python: "3.13" +# Configures the Sphinx documentation build to use the configuration file located at docs/source/conf.py sphinx: configuration: docs/source/conf.py +# Configures the Python environment to be used; the documentation uses the linkcode extension, to generate links to the source code on GitHub; to +# produce the links, the extension needs ViRelAy to be installed; also, the "docs" extra requirements are installed, which contains the Sphinx +# extensions used in the documentation python: install: - method: pip diff --git a/CHANGELOG.md b/CHANGELOG.md index 320a816..24ab198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - All references to the old logo were updated to point to the new location. The URL used in the read me was made absolute, because the read me is also used for the PyPI package and PyPI would not be able to resolve the relative URL to the logo on GitHub. - The project is dual-licensed under the GNU General Public License Version 3 (GPL-3.0) or later, and the GNU Lesser General Public License Version 3 (LGPL-3.0) or later. The GPL-3.0 license is in the `COPYING` file and the LGPL-3.0 license is in the `COPYING.LESSER` file. Additionally, there used to be a `LICENSE` file, which contained a note about the dual-licensing. This was, however, confusing, as GitHub does not recognize that the file is only a note about the dual-licensing and not the actual license. The `LICENSE` file was removed and the note about the dual-licensing was added to the read me. - Added a `CITATION.cff` file, which contains the necessary information to cite this repository. This file is based on the [Citation File Format (CFF)](https://citation-file-format.github.io) standard. This file is supported by GitHub and results in a "Cite this repository" button on the website, which allows users to directly generate a proper citation for the repository in multiple different formats. +- The configuration for the GitLab CI, which was stored in the `.gitlab-ci.yml` file, was removed. The project is no longer being hosted on GitLab, and the CI configuration is no longer needed. ### CoRelAy Updates in v0.3.0 @@ -27,9 +28,12 @@ - The tox configuration was updated and now uses tox-uv to run all commands via uv instead of directly creating environments. This means, that all Python environments can now be run without having to install multiple Python versions. - The tox configuration was also cleaned up. - Support for Python 3.7 was removed, not only because it has already reached its end-of-life, but also because some of the dependencies (especially tox-uv) do not support it anymore. The two remaining supported Python versions (3.8 and 3.9) are now recorded in the `.python-versions` file, which makes it trivial to install them using uv. +- Updated the Python dependencies in the `pyproject.toml` to their respective latest versions. +- Some of the new dependency versions no longer support Python 3.8 and Python 3.9 (as well as Python 3.10). For this reason, the project was migrated to support Python 3.11, 3.12, and 3.13. The `.python-versions` file, the tox configuration, and the GitHub Actions workflow were updated to reflect this change. - The unit tests were moved from the `tests` folder to `tests/unit_tests`, which clears up space for other test files. - The tox configuration was moved from the root directory to the `tests` directory, which is more appropriate, as tox is mostly used for testing and linting. - The configurations for the linters PyLint and Flake8 were moved from the root directory of the repository (in the case of PyLint) and from the tox configuration file (in the case of Flake8) into the `tests/linters` directory. The CSpell configuration was also moved there. +- The `corelay/version.py` file was deleted as it is automatically generated during the build process and should not be checked into source control. ### CI/CD Updates in v0.3.0 @@ -43,8 +47,10 @@ ### Documentation Updates in v0.3.0 +- The configuration for Read the Docs was updated to use the latest available versions of Ubuntu (24.04) and Python (3.12). It was also documented. - The logo was added to the documentation, which previously contained a copy of the logo in the `docs/images` directory, but did not include it. The version contained in the `docs/images` directory was removed and the index page now directly references the logo in the `design` directory. - The favicon used in the documentation was updated to use the SVG version of the logo without the title. Previously, it was still the old "S" logo, which was from before CoRelAy was renamed from Sprincl. +- One of the new dependency versions (`metrohash-python`) requires a C++ compiler to build and uses the `c++` command, which may not be available on all systems. To ensure that users are not confused by this, a note was added to the read me file explaining that the `c++` command is required to build the project and showing how to install it on Fedora (one of the systems that do not have the `c++` command installed by default). ## v0.2.1 diff --git a/README.md b/README.md index 0fcba98..065afc7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,13 @@ CoRelAy may be installed using pip with $ pip install corelay ``` +> [!NOTE] +> If you experience issues installing the `metrohash-python` dependency, this may be due to the `c++` command being missing. For example, under Fedora, the `gcc-c++` package has to be installed to make the `c++` command available. You can install it using + +```shell +$ sudo dnf install gcc-c++ +``` + To install optional HDBSCAN and UMAP support, use ```shell diff --git a/source/.python-versions b/source/.python-versions index 5f2806b..3e5dec7 100644 --- a/source/.python-versions +++ b/source/.python-versions @@ -1,2 +1,3 @@ -3.8.20 -3.9.21 +3.11.12 +3.12.10 +3.13.3 diff --git a/source/corelay/version.py b/source/corelay/version.py deleted file mode 100644 index 246de05..0000000 --- a/source/corelay/version.py +++ /dev/null @@ -1,21 +0,0 @@ -# file generated by setuptools-scm -# don't change, don't track in version control - -__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"] - -TYPE_CHECKING = False -if TYPE_CHECKING: - from typing import Tuple - from typing import Union - - VERSION_TUPLE = Tuple[Union[int, str], ...] -else: - VERSION_TUPLE = object - -version: str -__version__: str -__version_tuple__: VERSION_TUPLE -version_tuple: VERSION_TUPLE - -__version__ = version = '0.2.2.dev7+ga262299.d20250417' -__version_tuple__ = version_tuple = (0, 2, 2, 'dev7', 'ga262299.d20250417') diff --git a/source/pyproject.toml b/source/pyproject.toml index 833887e..be946f8 100644 --- a/source/pyproject.toml +++ b/source/pyproject.toml @@ -2,11 +2,11 @@ # The project's basic metadata and dependencies [project] -# General project information, the Project supports Python 3.7, 3.8, and 3.9; the version of the project is automatically determined from the latest -# Git tag using the Hatch-VCS plugin and the read me is dynamically generated using the Hatch Fancy PyPI Readme plugin (this is necessary, because -# Hatch cannot access the root directory of the repository while building the wheel, which is where the read me is located; Hatch first builds the -# source distribution from the files in the project directory, from where it can access top-level repository files, and then builds the wheel from the -# source distribution by extracting it into a temporary directory, from where it cannot access the top-level repository files) +# General project information, the Project supports Python 3.11, 3.12, and 3.13; the version of the project is automatically determined from the +# latest Git tag using the Hatch-VCS plugin and the read me is dynamically generated using the Hatch Fancy PyPI Readme plugin (this is necessary, +# because Hatch cannot access the root directory of the repository while building the wheel, which is where the read me is located; Hatch first builds +# the source distribution from the files in the project directory, from where it can access top-level repository files, and then builds the wheel from +# the source distribution by extracting it into a temporary directory, from where it cannot access the top-level repository files) name = "corelay" description = """\ CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines to generate analysis data which can then be visualized using @@ -24,7 +24,7 @@ keywords = [ "Artificial Intelligence" ] license = "GPL-3.0-or-later AND LGPL-3.0-or-later" -requires-python = ">=3.8" +requires-python = ">=3.11.12" dynamic = [ "readme", "version" @@ -44,14 +44,14 @@ authors = [ # The project's dependencies, which are included in the requirements of the published package dependencies = [ - "Click>=7.0,<8.0.0", - "h5py>=3.0.0,<4.0.0", - "matplotlib>=3.0.3,<4.0.0", - "metrohash-python>=1.1.3.post2,<2.0.0", - "numpy>=1.16.3,<2.0.0", - "scikit-image>=0.18.0,<1.0.0", - "scikit-learn>=0.20.3,<1.0.0", - "scipy>=1.2.1,<2.0.0" + "click>=8.1.8,<9.0.0", + "h5py>=3.13.0,<4.0.0", + "matplotlib>=3.10.1,<4.0.0", + "metrohash-python>=1.1.3.3,<2.0.0", + "numpy>=2.2.4,<3.0.0", + "scikit-image>=0.25.2,<1.0.0", + "scikit-learn>=1.6.1,<2.0.0", + "scipy>=1.15.2,<2.0.0" ] # The PyPI classifiers that describe the project @@ -62,8 +62,9 @@ classifiers = [ "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Typing :: Typed" ] @@ -81,31 +82,31 @@ Changelog = "https://github.com/virelay/corelay/blob/main/CHANGELOG.md" dev-dependencies = [ # Dependencies required for building the documentation - "sphinx>=4.2.0,<5.0.0", - "sphinx-copybutton>=0.4.0,<1.0.0", - "sphinx-rtd-theme>=1.0.0,<2.0.0", - "sphinxcontrib.bibtex>=2.4.1,<3.0.0", - "sphinxcontrib.datatemplates>=0.9.0,<1.0.0", + "sphinx>=8.2.3,<9.0.0", + "sphinx-copybutton>=0.5.2,<1.0.0", + "sphinx-rtd-theme>=3.0.2,<4.0.0", + "sphinxcontrib.bibtex>=2.6.3,<3.0.0", + "sphinxcontrib.datatemplates>=0.11.0,<1.0.0", # Dependencies required for testing - "tox>=4.12.1,<5.0.0", - "tox-uv>=1.0.0,<2.0.0", - "coverage>=5.5.0,<6.0.0", - "pytest-cov>=2.12.1,<3.0.0", - "pytest>=6.2.5,<7.0.0", - "setuptools>=58.1.0,<59.0.0", + "tox>=4.25.0,<5.0.0", + "tox-uv>=1.25.0,<2.0.0", + "coverage>=7.8.0,<8.0.0", + "pytest-cov>=6.1.1,<7.0.0", + "pytest>=8.3.5,<9.0.0", + "setuptools>=78.1.0,<79.0.0", # Dependencies required for linting - "flake8>=3.9.2,<4.0.0", - "pylint>=2.17.7,<3.0.0" + "flake8>=7.2.0,<8.0.0", + "pylint>=3.3.6,<4.0.0" ] # Configures which build system is used by UV to build the project [build-system] requires = [ - "hatchling>=0.8.0,<1.0.0", - "hatch-vcs>=0.1.0,<1.0.0", - "hatch-fancy-pypi-readme>=22.1.0,<23.0.0" + "hatchling>=1.27.0,<2.0.0", + "hatch-vcs>=0.4.0,<1.0.0", + "hatch-fancy-pypi-readme>=24.1.0,<25.0.0" ] build-backend = "hatchling.build" diff --git a/source/uv.lock b/source/uv.lock index 92feed7..2e9f27d 100644 --- a/source/uv.lock +++ b/source/uv.lock @@ -1,36 +1,15 @@ version = 1 revision = 1 -requires-python = ">=3.8" +requires-python = ">=3.11.12" resolution-markers = [ "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", - "python_full_version < '3.9'", -] - -[[package]] -name = "alabaster" -version = "0.7.13" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/94/71/a8ee96d1fd95ca04a0d2e2d9c4081dac4c2d2b12f7ddb899c8cb9bfd1532/alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2", size = 11454 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/88/c7083fc61120ab661c5d0b82cb77079fc1429d3f913a456c1c82cf4658f7/alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3", size = 13857 }, + "python_full_version < '3.12'", ] [[package]] name = "alabaster" version = "0.7.16" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } wheels = [ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, @@ -38,40 +17,17 @@ wheels = [ [[package]] name = "astroid" -version = "2.15.8" +version = "3.3.9" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lazy-object-proxy" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/3d/c18b0854d0d2eb3aca20c149cff5c90e6b84a5366066768d98636f5045ed/astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a", size = 344362 } +sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/b6/c0b5394ec6149e0129421f1a762b805e0e583974bc3cd65e3c7ce7c95444/astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c", size = 278329 }, -] - -[[package]] -name = "atomicwrites" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/c6/53da25344e3e3a9c01095a89f16dbcda021c609ddb42dd6d7c0528236fb2/atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11", size = 14227 } - -[[package]] -name = "attrs" -version = "25.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, + { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339 }, ] [[package]] name = "babel" version = "2.17.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytz", marker = "python_full_version < '3.9'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, @@ -110,19 +66,6 @@ version = "3.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, @@ -162,42 +105,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/10/bd/6517ea94f2672e801011d50b5d06be2a0deaf566aea27bcdcd47e5195357/charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", size = 195653 }, - { url = "https://files.pythonhosted.org/packages/e5/0d/815a2ba3f283b4eeaa5ece57acade365c5b4135f65a807a083c818716582/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", size = 140701 }, - { url = "https://files.pythonhosted.org/packages/aa/17/c94be7ee0d142687e047fe1de72060f6d6837f40eedc26e87e6e124a3fc6/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", size = 150495 }, - { url = "https://files.pythonhosted.org/packages/f7/33/557ac796c47165fc141e4fb71d7b0310f67e05cb420756f3a82e0a0068e0/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", size = 142946 }, - { url = "https://files.pythonhosted.org/packages/1e/0d/38ef4ae41e9248d63fc4998d933cae22473b1b2ac4122cf908d0f5eb32aa/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", size = 144737 }, - { url = "https://files.pythonhosted.org/packages/43/01/754cdb29dd0560f58290aaaa284d43eea343ad0512e6ad3b8b5c11f08592/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", size = 147471 }, - { url = "https://files.pythonhosted.org/packages/ba/cd/861883ba5160c7a9bd242c30b2c71074cda2aefcc0addc91118e0d4e0765/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", size = 140801 }, - { url = "https://files.pythonhosted.org/packages/6f/7f/0c0dad447819e90b93f8ed238cc8f11b91353c23c19e70fa80483a155bed/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", size = 149312 }, - { url = "https://files.pythonhosted.org/packages/8e/09/9f8abcc6fff60fb727268b63c376c8c79cc37b833c2dfe1f535dfb59523b/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", size = 152347 }, - { url = "https://files.pythonhosted.org/packages/be/e5/3f363dad2e24378f88ccf63ecc39e817c29f32e308ef21a7a6d9c1201165/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", size = 149888 }, - { url = "https://files.pythonhosted.org/packages/e4/10/a78c0e91f487b4ad0ef7480ac765e15b774f83de2597f1b6ef0eaf7a2f99/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", size = 145169 }, - { url = "https://files.pythonhosted.org/packages/d3/81/396e7d7f5d7420da8273c91175d2e9a3f569288e3611d521685e4b9ac9cc/charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", size = 95094 }, - { url = "https://files.pythonhosted.org/packages/40/bb/20affbbd9ea29c71ea123769dc568a6d42052ff5089c5fe23e21e21084a6/charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", size = 102139 }, - { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, - { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, - { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, - { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, - { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, - { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, - { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, - { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, - { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, - { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, - { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, - { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, - { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, ] [[package]] name = "click" -version = "7.1.2" +version = "8.1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/6f/be940c8b1f1d69daceeb0032fee6c34d7bd70e3e649ccac0951500b4720e/click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", size = 297279 } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/3d/fa76db83bf75c4f8d338c2fd15c8d33fdd7ad23a9b5e57eb6c5de26b430e/click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc", size = 82780 }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, ] [[package]] @@ -209,173 +129,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] -[[package]] -name = "contourpy" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/7d/087ee4295e7580d3f7eb8a8a4e0ec8c7847e60f34135248ccf831cf5bbfc/contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab", size = 13433167 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/7f/c44a51a83a093bf5c84e07dd1e3cfe9f68c47b6499bd05a9de0c6dbdc2bc/contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b", size = 247207 }, - { url = "https://files.pythonhosted.org/packages/a9/65/544d66da0716b20084874297ff7596704e435cf011512f8e576638e83db2/contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d", size = 232428 }, - { url = "https://files.pythonhosted.org/packages/5b/e6/697085cc34a294bd399548fd99562537a75408f113e3a815807e206246f0/contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae", size = 285304 }, - { url = "https://files.pythonhosted.org/packages/69/4b/52d0d2e85c59f00f6ddbd6fea819f267008c58ee7708da96d112a293e91c/contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916", size = 322655 }, - { url = "https://files.pythonhosted.org/packages/82/fc/3decc656a547a6d5d5b4249f81c72668a1f3259a62b2def2504120d38746/contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0", size = 296430 }, - { url = "https://files.pythonhosted.org/packages/f1/6b/e4b0f8708f22dd7c321f87eadbb98708975e115ac6582eb46d1f32197ce6/contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1", size = 301672 }, - { url = "https://files.pythonhosted.org/packages/c3/87/201410522a756e605069078833d806147cad8532fdc164a96689d05c5afc/contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d", size = 820145 }, - { url = "https://files.pythonhosted.org/packages/b4/d9/42680a17d43edda04ab2b3f11125cf97b61bce5d3b52721a42960bf748bd/contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431", size = 399542 }, - { url = "https://files.pythonhosted.org/packages/55/14/0dc1884e3c04f9b073a47283f5d424926644250891db392a07c56f05e5c5/contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb", size = 477974 }, - { url = "https://files.pythonhosted.org/packages/8b/4f/be28a39cd5e988b8d3c2cc642c2c7ffeeb28fe80a86df71b6d1e473c5038/contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2", size = 248613 }, - { url = "https://files.pythonhosted.org/packages/2c/8e/656f8e7cd316aa68d9824744773e90dbd71f847429d10c82001e927480a2/contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b", size = 233603 }, - { url = "https://files.pythonhosted.org/packages/60/2a/4d4bd4541212ab98f3411f21bf58b0b246f333ae996e9f57e1acf12bcc45/contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b", size = 287037 }, - { url = "https://files.pythonhosted.org/packages/24/67/8abf919443381585a4eee74069e311c736350549dae02d3d014fef93d50a/contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532", size = 323274 }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6da11329dd35a2f2e404a95e5374b5702de6ac52e776e8b87dd6ea4b29d0/contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e", size = 297801 }, - { url = "https://files.pythonhosted.org/packages/b7/f6/78f60fa0b6ae64971178e2542e8b3ad3ba5f4f379b918ab7b18038a3f897/contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5", size = 302821 }, - { url = "https://files.pythonhosted.org/packages/da/25/6062395a1c6a06f46a577da821318886b8b939453a098b9cd61671bb497b/contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62", size = 820121 }, - { url = "https://files.pythonhosted.org/packages/41/5e/64e78b1e8682cbab10c13fc1a2c070d30acedb805ab2f42afbd3d88f7225/contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33", size = 401590 }, - { url = "https://files.pythonhosted.org/packages/e5/76/94bc17eb868f8c7397f8fdfdeae7661c1b9a35f3a7219da308596e8c252a/contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45", size = 480534 }, - { url = "https://files.pythonhosted.org/packages/94/0f/07a5e26fec7176658f6aecffc615900ff1d303baa2b67bc37fd98ce67c87/contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a", size = 249799 }, - { url = "https://files.pythonhosted.org/packages/32/0b/d7baca3f60d3b3a77c9ba1307c7792befd3c1c775a26c649dca1bfa9b6ba/contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e", size = 232739 }, - { url = "https://files.pythonhosted.org/packages/6d/62/a385b4d4b5718e3a933de5791528f45f1f5b364d3c79172ad0309c832041/contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442", size = 282171 }, - { url = "https://files.pythonhosted.org/packages/91/21/8c6819747fea53557f3963ca936035b3e8bed87d591f5278ad62516a059d/contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8", size = 321182 }, - { url = "https://files.pythonhosted.org/packages/22/29/d75da9002f9df09c755b12cf0357eb91b081c858e604f4e92b4b8bfc3c15/contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7", size = 295869 }, - { url = "https://files.pythonhosted.org/packages/a7/47/4e7e66159f881c131e3b97d1cc5c0ea72be62bdd292c7f63fd13937d07f4/contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf", size = 298756 }, - { url = "https://files.pythonhosted.org/packages/d3/bb/bffc99bc3172942b5eda8027ca0cb80ddd336fcdd634d68adce957d37231/contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d", size = 818441 }, - { url = "https://files.pythonhosted.org/packages/da/1b/904baf0aaaf6c6e2247801dcd1ff0d7bf84352839927d356b28ae804cbb0/contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6", size = 410294 }, - { url = "https://files.pythonhosted.org/packages/75/d4/c3b7a9a0d1f99b528e5a46266b0b9f13aad5a0dd1156d071418df314c427/contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970", size = 486678 }, - { url = "https://files.pythonhosted.org/packages/02/7e/ffaba1bf3719088be3ad6983a5e85e1fc9edccd7b406b98e433436ecef74/contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d", size = 247023 }, - { url = "https://files.pythonhosted.org/packages/a6/82/29f5ff4ae074c3230e266bc9efef449ebde43721a727b989dd8ef8f97d73/contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9", size = 232380 }, - { url = "https://files.pythonhosted.org/packages/9b/cb/08f884c4c2efd433a38876b1b8069bfecef3f2d21ff0ce635d455962f70f/contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217", size = 285830 }, - { url = "https://files.pythonhosted.org/packages/8e/57/cd4d4c99d999a25e9d518f628b4793e64b1ecb8ad3147f8469d8d4a80678/contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684", size = 322038 }, - { url = "https://files.pythonhosted.org/packages/32/b6/c57ed305a6f86731107fc183e97c7e6a6005d145f5c5228a44718082ad12/contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce", size = 295797 }, - { url = "https://files.pythonhosted.org/packages/8e/71/7f20855592cc929bc206810432b991ec4c702dc26b0567b132e52c85536f/contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8", size = 301124 }, - { url = "https://files.pythonhosted.org/packages/86/6d/52c2fc80f433e7cdc8624d82e1422ad83ad461463cf16a1953bbc7d10eb1/contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251", size = 819787 }, - { url = "https://files.pythonhosted.org/packages/d0/b0/f8d4548e89f929d6c5ca329df9afad6190af60079ec77d8c31eb48cf6f82/contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7", size = 400031 }, - { url = "https://files.pythonhosted.org/packages/96/1b/b05cd42c8d21767a0488b883b38658fb9a45f86c293b7b42521a8113dc5d/contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9", size = 477949 }, - { url = "https://files.pythonhosted.org/packages/16/d9/8a15ff67fc27c65939e454512955e1b240ec75cd201d82e115b3b63ef76d/contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba", size = 247396 }, - { url = "https://files.pythonhosted.org/packages/09/fe/086e6847ee53da10ddf0b6c5e5f877ab43e68e355d2f4c85f67561ee8a57/contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34", size = 232598 }, - { url = "https://files.pythonhosted.org/packages/a3/9c/662925239e1185c6cf1da8c334e4c61bddcfa8e528f4b51083b613003170/contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887", size = 286436 }, - { url = "https://files.pythonhosted.org/packages/d3/7e/417cdf65da7140981079eda6a81ecd593ae0239bf8c738f2e2b3f6df8920/contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718", size = 322629 }, - { url = "https://files.pythonhosted.org/packages/a8/22/ffd88aef74cc045698c5e5c400e8b7cd62311199c109245ac7827290df2c/contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f", size = 297117 }, - { url = "https://files.pythonhosted.org/packages/2b/c0/24c34c41a180f875419b536125799c61e2330b997d77a5a818a3bc3e08cd/contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85", size = 301855 }, - { url = "https://files.pythonhosted.org/packages/bf/ec/f9877f6378a580cd683bd76c8a781dcd972e82965e0da951a739d3364677/contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e", size = 820597 }, - { url = "https://files.pythonhosted.org/packages/e1/3a/c41f4bc7122d3a06388acae1bed6f50a665c1031863ca42bd701094dcb1f/contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0", size = 400031 }, - { url = "https://files.pythonhosted.org/packages/87/2b/9b49451f7412cc1a79198e94a771a4e52d65c479aae610b1161c0290ef2c/contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887", size = 435965 }, - { url = "https://files.pythonhosted.org/packages/e6/3c/fc36884b6793e2066a6ff25c86e21b8bd62553456b07e964c260bcf22711/contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e", size = 246493 }, - { url = "https://files.pythonhosted.org/packages/3d/85/f4c5b09ce79828ed4553a8ae2ebdf937794f57b45848b1f5c95d9744ecc2/contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3", size = 289240 }, - { url = "https://files.pythonhosted.org/packages/18/d3/9d7c0a372baf5130c1417a4b8275079d5379c11355436cb9fc78af7d7559/contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23", size = 476043 }, - { url = "https://files.pythonhosted.org/packages/e7/12/643242c3d9b031ca19f9a440f63e568dd883a04711056ca5d607f9bda888/contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb", size = 246247 }, - { url = "https://files.pythonhosted.org/packages/e1/37/95716fe235bf441422059e4afcd4b9b7c5821851c2aee992a06d1e9f831a/contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163", size = 289029 }, - { url = "https://files.pythonhosted.org/packages/e5/fd/14852c4a688031e0d8a20d9a1b60078d45507186ef17042093835be2f01a/contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c", size = 476043 }, -] - -[[package]] -name = "contourpy" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145 }, - { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808 }, - { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681 }, - { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643 }, - { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/f1/17/c52d2970784383cafb0bd918b6fb036d98d96bbf0bc1befb5d1e31a07a70/contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5", size = 171353 }, - { url = "https://files.pythonhosted.org/packages/53/23/db9f69676308e094d3c45f20cc52e12d10d64f027541c995d89c11ad5c75/contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843", size = 211817 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/38/1e/94bda024d629f254143a134eead69e21c836429a2a6ce82209a00ddcb79a/contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb", size = 215838 }, -] - [[package]] name = "contourpy" version = "1.3.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534 }, - { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188 }, { 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 }, { 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 }, { 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 }, @@ -416,9 +178,6 @@ wheels = [ { 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 }, { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550 }, { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599 }, { 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 }, { 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 }, { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791 }, @@ -429,21 +188,13 @@ name = "corelay" source = { editable = "." } dependencies = [ { name = "click" }, - { name = "h5py", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "h5py", version = "3.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "matplotlib", version = "3.7.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "matplotlib", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "h5py" }, + { name = "matplotlib" }, { name = "metrohash-python" }, - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "scikit-image", version = "0.21.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "scikit-image", version = "0.24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy" }, + { name = "scikit-image" }, { name = "scikit-learn" }, - { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "scipy", version = "1.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scipy" }, ] [package.dev-dependencies] @@ -460,62 +211,86 @@ dev = [ { name = "sphinxcontrib-bibtex" }, { name = "sphinxcontrib-datatemplates" }, { name = "tox" }, - { name = "tox-uv", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "tox-uv", version = "1.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "tox-uv" }, ] [package.metadata] requires-dist = [ - { name = "click", specifier = ">=7.0,<8.0.0" }, - { name = "h5py", specifier = ">=3.0.0,<4.0.0" }, - { name = "matplotlib", specifier = ">=3.0.3,<4.0.0" }, - { name = "metrohash-python", specifier = ">=1.1.3.post2,<2.0.0" }, - { name = "numpy", specifier = ">=1.16.3,<2.0.0" }, - { name = "scikit-image", specifier = ">=0.18.0,<1.0.0" }, - { name = "scikit-learn", specifier = ">=0.20.3,<1.0.0" }, - { name = "scipy", specifier = ">=1.2.1,<2.0.0" }, + { name = "click", specifier = ">=8.1.8,<9.0.0" }, + { name = "h5py", specifier = ">=3.13.0,<4.0.0" }, + { name = "matplotlib", specifier = ">=3.10.1,<4.0.0" }, + { name = "metrohash-python", specifier = ">=1.1.3.3,<2.0.0" }, + { name = "numpy", specifier = ">=2.2.4,<3.0.0" }, + { name = "scikit-image", specifier = ">=0.25.2,<1.0.0" }, + { name = "scikit-learn", specifier = ">=1.6.1,<2.0.0" }, + { name = "scipy", specifier = ">=1.15.2,<2.0.0" }, ] [package.metadata.requires-dev] dev = [ - { name = "coverage", specifier = ">=5.5.0,<6.0.0" }, - { name = "flake8", specifier = ">=3.9.2,<4.0.0" }, - { name = "pylint", specifier = ">=2.17.7,<3.0.0" }, - { name = "pytest", specifier = ">=6.2.5,<7.0.0" }, - { name = "pytest-cov", specifier = ">=2.12.1,<3.0.0" }, - { name = "setuptools", specifier = ">=58.1.0,<59.0.0" }, - { name = "sphinx", specifier = ">=4.2.0,<5.0.0" }, - { name = "sphinx-copybutton", specifier = ">=0.4.0,<1.0.0" }, - { name = "sphinx-rtd-theme", specifier = ">=1.0.0,<2.0.0" }, - { name = "sphinxcontrib-bibtex", specifier = ">=2.4.1,<3.0.0" }, - { name = "sphinxcontrib-datatemplates", specifier = ">=0.9.0,<1.0.0" }, - { name = "tox", specifier = ">=4.12.1,<5.0.0" }, - { name = "tox-uv", specifier = ">=1.0.0,<2.0.0" }, + { name = "coverage", specifier = ">=7.8.0,<8.0.0" }, + { name = "flake8", specifier = ">=7.2.0,<8.0.0" }, + { name = "pylint", specifier = ">=3.3.6,<4.0.0" }, + { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, + { name = "pytest-cov", specifier = ">=6.1.1,<7.0.0" }, + { name = "setuptools", specifier = ">=78.1.0,<79.0.0" }, + { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, + { name = "sphinx-copybutton", specifier = ">=0.5.2,<1.0.0" }, + { name = "sphinx-rtd-theme", specifier = ">=3.0.2,<4.0.0" }, + { name = "sphinxcontrib-bibtex", specifier = ">=2.6.3,<3.0.0" }, + { name = "sphinxcontrib-datatemplates", specifier = ">=0.11.0,<1.0.0" }, + { name = "tox", specifier = ">=4.25.0,<5.0.0" }, + { name = "tox-uv", specifier = ">=1.25.0,<2.0.0" }, ] [[package]] name = "coverage" -version = "5.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/df/d5e67851e83948def768d7fb1a0fd373665b20f56ff63ed220c6cd16cb11/coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", size = 691258 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/a2/43dd30964103a7ff1fd03392a30a5b08105bc85d1bafbfc51023a1bb4fd3/coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", size = 207511 }, - { url = "https://files.pythonhosted.org/packages/d4/3e/4f6451b8b09a1eb2d0e7f61a3d7019bd98d556fc5343378f76e8905b2789/coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", size = 238991 }, - { url = "https://files.pythonhosted.org/packages/a2/a2/10974646b530b043a04cfe3ffa38a0f3b31a04c3b19cc68b9627b2c8e8dc/coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", size = 211489 }, - { url = "https://files.pythonhosted.org/packages/b6/26/b53bf0fef1b4bce6f7d61fef10fbf924d943987d4c9e53c394ecebff3673/coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", size = 207633 }, - { url = "https://files.pythonhosted.org/packages/62/05/9f43545401929bd0f49a2e0e4e38f69f6f7a59c1be88d1f63c34aebd7684/coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", size = 243719 }, - { url = "https://files.pythonhosted.org/packages/2b/7c/841e2d203dd1df07ceba0f844e5011e4341ce854207687d4c4d68ab8ff95/coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", size = 245320 }, - { url = "https://files.pythonhosted.org/packages/0a/eb/c6975d34252096a06883726f1898cd62da9b9ccafc2ed9a4f279fe45999e/coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", size = 243721 }, - { url = "https://files.pythonhosted.org/packages/a4/3a/8f7b217265503eae2b0ea97e714e2709e1e84ee13cd3ca6abdff1e99e76c/coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", size = 245325 }, - { url = "https://files.pythonhosted.org/packages/15/a4/5d5b5e2001d384b97cdaf3f19f52370a5abe9a5b1db876c580418bcb1151/coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", size = 210316 }, - { url = "https://files.pythonhosted.org/packages/e9/92/45878381df409a4b5e5a6399e522964cfd4e6976d07f6300e97eebd6e307/coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", size = 211496 }, - { url = "https://files.pythonhosted.org/packages/0d/8a/3b13c4e1f241a7083a4ee9986b969f0238f41dcd7a8990c786bc3b4b5b19/coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", size = 207640 }, - { url = "https://files.pythonhosted.org/packages/1d/7d/57fe5de17d64f1be36a75aeef15dda8fdd64a241612bd79071b9d79e9bed/coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", size = 242068 }, - { url = "https://files.pythonhosted.org/packages/21/77/0ae9b19cb99d0a41b740535d5f9502e9bc6b32a24eff6b0bcb182e2cce56/coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", size = 243533 }, - { url = "https://files.pythonhosted.org/packages/d3/f9/798152327a3fef063f66528493a74316501cff54e09bfdcd50723229ce15/coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", size = 242068 }, - { url = "https://files.pythonhosted.org/packages/a4/79/625f1ed5da2a69f52fb44e0b7ca1b470437ff502348c716005a98a71cd49/coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", size = 243537 }, - { url = "https://files.pythonhosted.org/packages/76/7b/81dafcea3845e8bf1baf126dacbb2c079d7ce30072fa1514262539911b5e/coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", size = 210316 }, - { url = "https://files.pythonhosted.org/packages/60/41/fa43e0321f0b6157eb017c6c098604a2fe02b553e4ea5aebe51f5dbbd612/coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", size = 211503 }, +version = "7.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493 }, + { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921 }, + { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556 }, + { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245 }, + { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032 }, + { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679 }, + { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852 }, + { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389 }, + { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997 }, + { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911 }, + { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684 }, + { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935 }, + { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994 }, + { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885 }, + { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142 }, + { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906 }, + { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124 }, + { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317 }, + { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170 }, + { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969 }, + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 }, + { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443 }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 }, ] [[package]] @@ -556,35 +331,17 @@ wheels = [ [[package]] name = "docutils" -version = "0.17.1" +version = "0.21.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/17/559b4d020f4b46e0287a2eddf2d8ebf76318fd3bd495f1625414b052fdc9/docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125", size = 2016138 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/5e/6003a0d1f37725ec2ebd4046b657abb9372202655f96e76795dca8c0063c/docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61", size = 575533 }, -] - -[[package]] -name = "filelock" -version = "3.16.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, ] [[package]] name = "filelock" version = "3.18.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } wheels = [ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, @@ -592,16 +349,16 @@ wheels = [ [[package]] name = "flake8" -version = "3.9.2" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mccabe" }, { name = "pycodestyle" }, { name = "pyflakes" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/47/15b267dfe7e03dca4c4c06e7eadbd55ef4dfd368b13a0bab36d708b14366/flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", size = 164777 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/80/35a0716e5d5101e643404dabd20f07f5528a21f3ef4032d31a49c913237b/flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907", size = 73147 }, + { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786 }, ] [[package]] @@ -610,14 +367,6 @@ version = "4.57.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/17/3ddfd1881878b3f856065130bb603f5922e81ae8a4eb53bce0ea78f765a8/fonttools-4.57.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:babe8d1eb059a53e560e7bf29f8e8f4accc8b6cfb9b5fd10e485bde77e71ef41", size = 2756260 }, - { url = "https://files.pythonhosted.org/packages/26/2b/6957890c52c030b0bf9e0add53e5badab4682c6ff024fac9a332bb2ae063/fonttools-4.57.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81aa97669cd726349eb7bd43ca540cf418b279ee3caba5e2e295fb4e8f841c02", size = 2284691 }, - { url = "https://files.pythonhosted.org/packages/cc/8e/c043b4081774e5eb06a834cedfdb7d432b4935bc8c4acf27207bdc34dfc4/fonttools-4.57.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0e9618630edd1910ad4f07f60d77c184b2f572c8ee43305ea3265675cbbfe7e", size = 4566077 }, - { url = "https://files.pythonhosted.org/packages/59/bc/e16ae5d9eee6c70830ce11d1e0b23d6018ddfeb28025fda092cae7889c8b/fonttools-4.57.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34687a5d21f1d688d7d8d416cb4c5b9c87fca8a1797ec0d74b9fdebfa55c09ab", size = 4608729 }, - { url = "https://files.pythonhosted.org/packages/25/13/e557bf10bb38e4e4c436d3a9627aadf691bc7392ae460910447fda5fad2b/fonttools-4.57.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69ab81b66ebaa8d430ba56c7a5f9abe0183afefd3a2d6e483060343398b13fb1", size = 4759646 }, - { url = "https://files.pythonhosted.org/packages/bc/c9/5e2952214d4a8e31026bf80beb18187199b7001e60e99a6ce19773249124/fonttools-4.57.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d639397de852f2ccfb3134b152c741406752640a266d9c1365b0f23d7b88077f", size = 4941652 }, - { url = "https://files.pythonhosted.org/packages/df/04/e80242b3d9ec91a1f785d949edc277a13ecfdcfae744de4b170df9ed77d8/fonttools-4.57.0-cp310-cp310-win32.whl", hash = "sha256:cc066cb98b912f525ae901a24cd381a656f024f76203bc85f78fcc9e66ae5aec", size = 2159432 }, - { url = "https://files.pythonhosted.org/packages/33/ba/e858cdca275daf16e03c0362aa43734ea71104c3b356b2100b98543dba1b/fonttools-4.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:7a64edd3ff6a7f711a15bd70b4458611fb240176ec11ad8845ccbab4fe6745db", size = 2203869 }, { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392 }, { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609 }, { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292 }, @@ -642,79 +391,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679 }, { url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833 }, { url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799 }, - { url = "https://files.pythonhosted.org/packages/8a/3f/c16dbbec7221783f37dcc2022d5a55f0d704ffc9feef67930f6eb517e8ce/fonttools-4.57.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d57b4e23ebbe985125d3f0cabbf286efa191ab60bbadb9326091050d88e8213", size = 2753756 }, - { url = "https://files.pythonhosted.org/packages/48/9f/5b4a3d6aed5430b159dd3494bb992d4e45102affa3725f208e4f0aedc6a3/fonttools-4.57.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:579ba873d7f2a96f78b2e11028f7472146ae181cae0e4d814a37a09e93d5c5cc", size = 2283179 }, - { url = "https://files.pythonhosted.org/packages/17/b2/4e887b674938b4c3848029a4134ac90dd8653ea80b4f464fa1edeae37f25/fonttools-4.57.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3e1ec10c29bae0ea826b61f265ec5c858c5ba2ce2e69a71a62f285cf8e4595", size = 4647139 }, - { url = "https://files.pythonhosted.org/packages/a5/0e/b6314a09a4d561aaa7e09de43fa700917be91e701f07df6178865962666c/fonttools-4.57.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1968f2a2003c97c4ce6308dc2498d5fd4364ad309900930aa5a503c9851aec8", size = 4691211 }, - { url = "https://files.pythonhosted.org/packages/bf/1d/b9f4b70d165c25f5c9aee61eb6ae90b0e9b5787b2c0a45e4f3e50a839274/fonttools-4.57.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:aff40f8ac6763d05c2c8f6d240c6dac4bb92640a86d9b0c3f3fff4404f34095c", size = 4873755 }, - { url = "https://files.pythonhosted.org/packages/3b/fa/a731c8f42ae2c6761d1c22bd3c90241d5b2b13cabb70598abc74a828b51f/fonttools-4.57.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d07f1b64008e39fceae7aa99e38df8385d7d24a474a8c9872645c4397b674481", size = 5070072 }, - { url = "https://files.pythonhosted.org/packages/1f/1e/6a988230109a2ba472e5de0a4c3936d49718cfc4b700b6bad53eca414bcf/fonttools-4.57.0-cp38-cp38-win32.whl", hash = "sha256:51d8482e96b28fb28aa8e50b5706f3cee06de85cbe2dce80dbd1917ae22ec5a6", size = 1484098 }, - { url = "https://files.pythonhosted.org/packages/dc/7a/2b3666e8c13d035adf656a8ae391380656144760353c97f74747c64fd3e5/fonttools-4.57.0-cp38-cp38-win_amd64.whl", hash = "sha256:03290e818782e7edb159474144fca11e36a8ed6663d1fcbd5268eb550594fd8e", size = 1529536 }, - { url = "https://files.pythonhosted.org/packages/d2/c7/3bddafbb95447f6fbabdd0b399bf468649321fd4029e356b4f6bd70fbc1b/fonttools-4.57.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7339e6a3283e4b0ade99cade51e97cde3d54cd6d1c3744459e886b66d630c8b3", size = 2758942 }, - { url = "https://files.pythonhosted.org/packages/d4/a2/8dd7771022e365c90e428b1607174c3297d5c0a2cc2cf4cdccb2221945b7/fonttools-4.57.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:05efceb2cb5f6ec92a4180fcb7a64aa8d3385fd49cfbbe459350229d1974f0b1", size = 2285959 }, - { url = "https://files.pythonhosted.org/packages/58/5a/2fd29c5e38b14afe1fae7d472373e66688e7c7a98554252f3cf44371e033/fonttools-4.57.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a97bb05eb24637714a04dee85bdf0ad1941df64fe3b802ee4ac1c284a5f97b7c", size = 4571677 }, - { url = "https://files.pythonhosted.org/packages/bf/30/b77cf81923f1a67ff35d6765a9db4718c0688eb8466c464c96a23a2e28d4/fonttools-4.57.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:541cb48191a19ceb1a2a4b90c1fcebd22a1ff7491010d3cf840dd3a68aebd654", size = 4616644 }, - { url = "https://files.pythonhosted.org/packages/06/33/376605898d8d553134144dff167506a49694cb0e0cf684c14920fbc1e99f/fonttools-4.57.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cdef9a056c222d0479a1fdb721430f9efd68268014c54e8166133d2643cb05d9", size = 4761314 }, - { url = "https://files.pythonhosted.org/packages/48/e4/e0e48f5bae04bc1a1c6b4fcd7d1ca12b29f1fe74221534b7ff83ed0db8fe/fonttools-4.57.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3cf97236b192a50a4bf200dc5ba405aa78d4f537a2c6e4c624bb60466d5b03bd", size = 4945563 }, - { url = "https://files.pythonhosted.org/packages/61/98/2dacfc6d70f2d93bde1bbf814286be343cb17f53057130ad3b843144dd00/fonttools-4.57.0-cp39-cp39-win32.whl", hash = "sha256:e952c684274a7714b3160f57ec1d78309f955c6335c04433f07d36c5eb27b1f9", size = 2159997 }, - { url = "https://files.pythonhosted.org/packages/93/fa/e61cc236f40d504532d2becf90c297bfed8e40abc0c8b08375fbb83eff29/fonttools-4.57.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2a722c0e4bfd9966a11ff55c895c817158fcce1b2b6700205a376403b546ad9", size = 2204508 }, { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605 }, ] -[[package]] -name = "h5py" -version = "3.11.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/8f/e557819155a282da36fb21f8de4730cfd10a964b52b3ae8d20157ac1c668/h5py-3.11.0.tar.gz", hash = "sha256:7b7e8f78072a2edec87c9836f25f34203fd492a4475709a18b417a33cfb21fa9", size = 406519 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/25/a1cc81b3a742b73f9409bafe4762c9de0940cce0955d4b6754698fd5ce44/h5py-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1625fd24ad6cfc9c1ccd44a66dac2396e7ee74940776792772819fc69f3a3731", size = 3477113 }, - { url = "https://files.pythonhosted.org/packages/d4/03/bbb9a992fb43d3ce46687b7c14107f0fa56e6c8704c9ca945a9392cbc8ce/h5py-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c072655ad1d5fe9ef462445d3e77a8166cbfa5e599045f8aa3c19b75315f10e5", size = 2939879 }, - { url = "https://files.pythonhosted.org/packages/94/00/94bf8573e7487b7c37f2b613fc381880d48ec2311f2e859b8a5817deb4df/h5py-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77b19a40788e3e362b54af4dcf9e6fde59ca016db2c61360aa30b47c7b7cef00", size = 5306122 }, - { url = "https://files.pythonhosted.org/packages/bb/0d/fbadb9c69e2a31f641bc24e8d21671129ef3b73f0c61bb16b094fadf1385/h5py-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef4e2f338fc763f50a8113890f455e1a70acd42a4d083370ceb80c463d803972", size = 2968816 }, - { url = "https://files.pythonhosted.org/packages/a0/52/38bb74cc4362738cc7ef819503fc54d70f0c3a7378519ccb0ac309389122/h5py-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbd732a08187a9e2a6ecf9e8af713f1d68256ee0f7c8b652a32795670fb481ba", size = 3489913 }, - { url = "https://files.pythonhosted.org/packages/f0/af/dfbea0c69fe725e9e77259d42f4e14eb582eb094200aaf697feb36f513d8/h5py-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75bd7b3d93fbeee40860fd70cdc88df4464e06b70a5ad9ce1446f5f32eb84007", size = 2946912 }, - { url = "https://files.pythonhosted.org/packages/af/26/f231ee425c8df93c1abbead3d90ea4a5ff3d6aa49e0edfd3b4c017e74844/h5py-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c416f8eb0daae39dabe71415cb531f95dce2d81e1f61a74537a50c63b28ab3", size = 5420165 }, - { url = "https://files.pythonhosted.org/packages/d8/5e/b7b83cfe60504cc4d24746aed04353af7ea8ec104e597e5ae71b8d0390cb/h5py-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:083e0329ae534a264940d6513f47f5ada617da536d8dccbafc3026aefc33c90e", size = 2979079 }, - { url = "https://files.pythonhosted.org/packages/58/a9/2655d4b8355d0ee783dc89dd40b5f0780e6f54a4c9b60721dc235fd6c457/h5py-3.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a76cae64080210389a571c7d13c94a1a6cf8cb75153044fd1f822a962c97aeab", size = 3466468 }, - { url = "https://files.pythonhosted.org/packages/9d/3f/cf80ef55e0a9b18aae96c763fbd275c54d0723e0f2cc54f954f87cc5c69a/h5py-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3736fe21da2b7d8a13fe8fe415f1272d2a1ccdeff4849c1421d2fb30fd533bc", size = 2943214 }, - { url = "https://files.pythonhosted.org/packages/db/7e/fedac8bb8c4729409e2dec5e4136a289116d701d54f69ce73c5617afc5f0/h5py-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6ae84a14103e8dc19266ef4c3e5d7c00b68f21d07f2966f0ca7bdb6c2761fb", size = 5378375 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/0ee327933ffa37af1fc7915df7fc067e6009adcd8445d55ad07a9bec11b5/h5py-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:21dbdc5343f53b2e25404673c4f00a3335aef25521bd5fa8c707ec3833934892", size = 2970991 }, - { url = "https://files.pythonhosted.org/packages/33/97/c1a8f28329ad794d18fc61bf251268ac03959bf93b82fdd7701ac6931fed/h5py-3.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:754c0c2e373d13d6309f408325343b642eb0f40f1a6ad21779cfa9502209e150", size = 3470228 }, - { url = "https://files.pythonhosted.org/packages/a4/1d/fd0b88c51c37bc8aeedecc4f4b48397f7ce13c87073aaf6912faec06e9f6/h5py-3.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:731839240c59ba219d4cb3bc5880d438248533366f102402cfa0621b71796b62", size = 2935809 }, - { url = "https://files.pythonhosted.org/packages/86/43/fd0bd74462b3c3fb35d98568935d3e5a435c8ec24d45ef408ac8869166af/h5py-3.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ec9df3dd2018904c4cc06331951e274f3f3fd091e6d6cc350aaa90fa9b42a76", size = 5309045 }, - { url = "https://files.pythonhosted.org/packages/15/9a/b5456e1acc4abb382938d4a730600823bfe77a4bbfd29140ccbf01ba5596/h5py-3.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:55106b04e2c83dfb73dc8732e9abad69d83a436b5b82b773481d95d17b9685e1", size = 2989172 }, - { url = "https://files.pythonhosted.org/packages/c2/1f/36a84945616881bd47e6c40dcdca7e929bc811725d78d001eddba6864185/h5py-3.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f4e025e852754ca833401777c25888acb96889ee2c27e7e629a19aee288833f0", size = 3490090 }, - { url = "https://files.pythonhosted.org/packages/3c/fb/e213586de5ea56f1747a843e725c62eef350512be57452186996ba660d52/h5py-3.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c4b760082626120031d7902cd983d8c1f424cdba2809f1067511ef283629d4b", size = 2951710 }, - { url = "https://files.pythonhosted.org/packages/71/28/69a881e01f198ccdb65c36f7adcfef22bfe85e38ffbfdf833af24f58eb5e/h5py-3.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67462d0669f8f5459529de179f7771bd697389fcb3faab54d63bf788599a48ea", size = 5326481 }, - { url = "https://files.pythonhosted.org/packages/c3/61/0b35ad9aac0ab0a33365879556fdb824fc83013df69b247386690db59015/h5py-3.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:d9c944d364688f827dc889cf83f1fca311caf4fa50b19f009d1f2b525edd33a3", size = 2978689 }, -] - [[package]] name = "h5py" version = "3.13.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/8a/bc76588ff1a254e939ce48f30655a8f79fac614ca8bd1eda1a79fa276671/h5py-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5540daee2b236d9569c950b417f13fd112d51d78b4c43012de05774908dff3f5", size = 3413286 }, - { url = "https://files.pythonhosted.org/packages/19/bd/9f249ecc6c517b2796330b0aab7d2351a108fdbd00d4bb847c0877b5533e/h5py-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:10894c55d46df502d82a7a4ed38f9c3fdbcb93efb42e25d275193e093071fade", size = 2915673 }, - { url = "https://files.pythonhosted.org/packages/72/71/0dd079208d7d3c3988cebc0776c2de58b4d51d8eeb6eab871330133dfee6/h5py-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb267ce4b83f9c42560e9ff4d30f60f7ae492eacf9c7ede849edf8c1b860e16b", size = 4283822 }, - { url = "https://files.pythonhosted.org/packages/d8/fa/0b6a59a1043c53d5d287effa02303bd248905ee82b25143c7caad8b340ad/h5py-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2cf6a231a07c14acd504a945a6e9ec115e0007f675bde5e0de30a4dc8d86a31", size = 4548100 }, - { url = "https://files.pythonhosted.org/packages/12/42/ad555a7ff7836c943fe97009405566dc77bcd2a17816227c10bd067a3ee1/h5py-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:851ae3a8563d87a5a0dc49c2e2529c75b8842582ccaefbf84297d2cfceeacd61", size = 2950547 }, { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922 }, { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619 }, { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366 }, @@ -730,11 +418,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255 }, { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580 }, { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890 }, - { url = "https://files.pythonhosted.org/packages/cd/91/3e5b4e4c399bb57141a2451c67808597ab6993f799587566c9f11dbaefe9/h5py-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82690e89c72b85addf4fc4d5058fb1e387b6c14eb063b0b879bf3f42c3b93c35", size = 3424729 }, - { url = "https://files.pythonhosted.org/packages/12/82/4e455e12e7ff26533c762eaf324edd6b076f84c3a003a40a1e52d805e0fb/h5py-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d571644958c5e19a61c793d8d23cd02479572da828e333498c9acc463f4a3997", size = 2926632 }, - { url = "https://files.pythonhosted.org/packages/ab/c9/fb430d3277e81eade92e54e87bd73e9f60c98240a86a5f43e3b85620d7d8/h5py-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:560e71220dc92dfa254b10a4dcb12d56b574d2d87e095db20466b32a93fec3f9", size = 4285580 }, - { url = "https://files.pythonhosted.org/packages/3f/9b/3e8cded7877ec84b707df82b9c6289cd1d7ad80fef9a10bb1389c5fee8f2/h5py-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10f061764d8dce0a9592ce08bfd5f243a00703325c388f1086037e5d619c5f1", size = 4550898 }, - { url = "https://files.pythonhosted.org/packages/cb/47/8353102cff9290861135e13eefff5a916855d2ab23bd052ec7ac144f4c48/h5py-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c82ece71ed1c2b807b6628e3933bc6eae57ea21dac207dca3470e3ceaaf437c", size = 2960208 }, ] [[package]] @@ -746,35 +429,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] -[[package]] -name = "imageio" -version = "2.35.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/bf/d0ddda79819405428f40e4bc9245c2b936a3a2b23d83b6e42d83822ef822/imageio-2.35.1.tar.gz", hash = "sha256:4952dfeef3c3947957f6d5dedb1f4ca31c6e509a476891062396834048aeed2a", size = 389686 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/b7/02adac4e42a691008b5cfb31db98c190e1fc348d1521b9be4429f9454ed1/imageio-2.35.1-py3-none-any.whl", hash = "sha256:6eb2e5244e7a16b85c10b5c2fe0f7bf961b40fcb9f1a9fd1bd1d2c2f8fb3cd65", size = 315378 }, -] - [[package]] name = "imageio" version = "2.37.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "numpy" }, + { name = "pillow" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963 } wheels = [ @@ -790,66 +451,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, ] -[[package]] -name = "importlib-metadata" -version = "8.5.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 }, -] - -[[package]] -name = "importlib-metadata" -version = "8.6.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "zipp", version = "3.21.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, -] - -[[package]] -name = "importlib-resources" -version = "6.4.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/be/f3e8c6081b684f176b761e6a2fef02a0be939740ed6f54109a2951d806f3/importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", size = 43372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, -] - -[[package]] -name = "importlib-resources" -version = "6.5.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "zipp", version = "3.21.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, -] - [[package]] name = "iniconfig" version = "2.1.0" @@ -873,8 +474,7 @@ name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markupsafe", version = "2.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "markupsafe", version = "3.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } wheels = [ @@ -890,157 +490,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, ] -[[package]] -name = "kiwisolver" -version = "1.4.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644 }, - { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877 }, - { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645 }, - { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022 }, - { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367 }, - { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884 }, - { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 }, - { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 }, - { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 }, - { url = "https://files.pythonhosted.org/packages/57/d6/620247574d9e26fe24384087879e8399e309f0051782f95238090afa6ccc/kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a", size = 122325 }, - { url = "https://files.pythonhosted.org/packages/bd/c6/572ad7d73dbd898cffa9050ffd7ff7e78a055a1d9b7accd6b4d1f50ec858/kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade", size = 65679 }, - { url = "https://files.pythonhosted.org/packages/14/a7/bb8ab10e12cc8764f4da0245d72dee4731cc720bdec0f085d5e9c6005b98/kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c", size = 64267 }, - { url = "https://files.pythonhosted.org/packages/54/a4/3b5a2542429e182a4df0528214e76803f79d016110f5e67c414a0357cd7d/kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95", size = 1387236 }, - { url = "https://files.pythonhosted.org/packages/a6/d7/bc3005e906c1673953a3e31ee4f828157d5e07a62778d835dd937d624ea0/kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b", size = 1500555 }, - { url = "https://files.pythonhosted.org/packages/09/a7/87cb30741f13b7af08446795dca6003491755805edc9c321fe996c1320b8/kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3", size = 1431684 }, - { url = "https://files.pythonhosted.org/packages/37/a4/1e4e2d8cdaa42c73d523413498445247e615334e39401ae49dae74885429/kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503", size = 1125811 }, - { url = "https://files.pythonhosted.org/packages/76/36/ae40d7a3171e06f55ac77fe5536079e7be1d8be2a8210e08975c7f9b4d54/kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf", size = 1179987 }, - { url = "https://files.pythonhosted.org/packages/d8/5d/6e4894b9fdf836d8bd095729dff123bbbe6ad0346289287b45c800fae656/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933", size = 2186817 }, - { url = "https://files.pythonhosted.org/packages/f0/2d/603079b2c2fd62890be0b0ebfc8bb6dda8a5253ca0758885596565b0dfc1/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e", size = 2332538 }, - { url = "https://files.pythonhosted.org/packages/bb/2a/9a28279c865c38a27960db38b07179143aafc94877945c209bfc553d9dd3/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89", size = 2293890 }, - { url = "https://files.pythonhosted.org/packages/1a/4d/4da8967f3bf13c764984b8fbae330683ee5fbd555b4a5624ad2b9decc0ab/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d", size = 2434677 }, - { url = "https://files.pythonhosted.org/packages/08/e9/a97a2b6b74dd850fa5974309367e025c06093a143befe9b962d0baebb4f0/kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5", size = 2250339 }, - { url = "https://files.pythonhosted.org/packages/8a/e7/55507a387ba1766e69f5e13a79e1aefabdafe0532bee5d1972dfc42b3d16/kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a", size = 46932 }, - { url = "https://files.pythonhosted.org/packages/52/77/7e04cca2ff1dc6ee6b7654cebe233de72b7a3ec5616501b6f3144fb70740/kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09", size = 55836 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/35/d3/5f2ecb94b5211c8a04f218a76133cc8d6d153b0f9cd0b45fad79907f0689/kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644", size = 46980 }, - { url = "https://files.pythonhosted.org/packages/ef/17/cd10d020578764ea91740204edc6b3236ed8106228a46f568d716b11feb2/kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4", size = 55847 }, - { url = "https://files.pythonhosted.org/packages/91/84/32232502020bd78d1d12be7afde15811c64a95ed1f606c10456db4e4c3ac/kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f", size = 48494 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715 }, - { url = "https://files.pythonhosted.org/packages/64/f3/2403d90821fffe496df16f6996cb328b90b0d80c06d2938a930a7732b4f1/kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00", size = 59662 }, - { url = "https://files.pythonhosted.org/packages/fa/7d/8f409736a4a6ac04354fa530ebf46682ddb1539b0bae15f4731ff2c575bc/kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935", size = 57753 }, - { url = "https://files.pythonhosted.org/packages/4c/a5/3937c9abe8eedb1356071739ad437a0b486cbad27d54f4ec4733d24882ac/kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b", size = 103564 }, - { url = "https://files.pythonhosted.org/packages/b2/18/a5ae23888f010b90d5eb8d196fed30e268056b2ded54d25b38a193bb70e9/kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d", size = 95264 }, - { url = "https://files.pythonhosted.org/packages/f9/d0/c4240ae86306d4395e9701f1d7e6ddcc6d60c28cb0127139176cfcfc9ebe/kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d", size = 78197 }, - { url = "https://files.pythonhosted.org/packages/62/db/62423f0ab66813376a35c1e7da488ebdb4e808fcb54b7cec33959717bda1/kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2", size = 56080 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811 }, -] - [[package]] name = "kiwisolver" version = "1.4.8" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623 }, - { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720 }, - { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413 }, - { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826 }, - { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231 }, - { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938 }, - { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799 }, - { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362 }, - { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695 }, - { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802 }, - { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646 }, - { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260 }, - { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633 }, - { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885 }, - { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175 }, { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, @@ -1099,12 +554,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, - { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403 }, - { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657 }, - { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948 }, - { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186 }, - { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279 }, - { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762 }, ] [[package]] @@ -1128,133 +577,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 }, ] -[[package]] -name = "lazy-object-proxy" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/f0/f02e2d150d581a294efded4020094a371bbab42423fe78625ac18854d89b/lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69", size = 43271 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/42/a96d9d153f6ea38b925494cb9b42cf4a9f98fd30cad3124fc22e9d04ec34/lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977", size = 27432 }, - { url = "https://files.pythonhosted.org/packages/4a/0d/b325461e43dde8d7644e9b9e9dd57f2a4af472b588c51ccbc92778e60ea4/lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3", size = 69133 }, - { url = "https://files.pythonhosted.org/packages/8b/fc/83711d743fb5aaca5747bbf225fe3b5cbe085c7f6c115856b5cce80f3224/lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05", size = 68272 }, - { url = "https://files.pythonhosted.org/packages/8d/b5/ea47215abd4da45791664d7bbfe2976ca0de2c37af38b5e9e6cf89e0e65e/lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895", size = 70891 }, - { url = "https://files.pythonhosted.org/packages/8b/9b/908e12e5fa265ea1579261ff80f7b2136fd2ba254bc7f4f7e3dba83fd0f2/lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83", size = 70451 }, - { url = "https://files.pythonhosted.org/packages/16/ab/d9a47f2e70767af5ee311d71109be6ef2991c66c77bfa18e66707edd9f8c/lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9", size = 25778 }, - { url = "https://files.pythonhosted.org/packages/74/d6/0104e4154d2c30227eb54491dda8a4132be046b4cb37fb4ce915a5abc0d5/lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4", size = 27551 }, - { url = "https://files.pythonhosted.org/packages/ff/e1/99a7ec68b892c9b8c6212617f54e7e9b0304d47edad8c0ff043ae3aeb1a9/lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c", size = 27434 }, - { url = "https://files.pythonhosted.org/packages/1a/76/6a41de4b44d1dcfe4c720d4606de0d7b69b6b450f0bdce16f2e1fb8abc89/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4", size = 70687 }, - { url = "https://files.pythonhosted.org/packages/1e/5d/eaa12126e8989c9bdd21d864cbba2b258cb9ee2f574ada1462a0004cfad8/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56", size = 69757 }, - { url = "https://files.pythonhosted.org/packages/53/a9/6f22cfe9572929656988b72c0de266c5d10755369b575322725f67364c4e/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9", size = 73709 }, - { url = "https://files.pythonhosted.org/packages/bd/e6/b10fd94710a99a6309f3ad61a4eb480944bbb17fcb41bd2d852fdbee57ee/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f", size = 73191 }, - { url = "https://files.pythonhosted.org/packages/c9/78/a9b9d314da02fe66b632f2354e20e40fc3508befb450b5a17987a222b383/lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03", size = 25773 }, - { url = "https://files.pythonhosted.org/packages/94/e6/e2d3b0c9efe61f72dc327ce2355941f540e0b0d1f2b3490cbab6bab7d3ea/lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6", size = 27550 }, - { url = "https://files.pythonhosted.org/packages/d0/5d/768a7f2ccebb29604def61842fd54f6f5f75c79e366ee8748dda84de0b13/lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba", size = 27560 }, - { url = "https://files.pythonhosted.org/packages/b3/ce/f369815549dbfa4bebed541fa4e1561d69e4f268a1f6f77da886df182dab/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43", size = 72403 }, - { url = "https://files.pythonhosted.org/packages/44/46/3771e0a4315044aa7b67da892b2fb1f59dfcf0eaff2c8967b2a0a85d5896/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9", size = 72401 }, - { url = "https://files.pythonhosted.org/packages/81/39/84ce4740718e1c700bd04d3457ac92b2e9ce76529911583e7a2bf4d96eb2/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3", size = 75375 }, - { url = "https://files.pythonhosted.org/packages/86/3b/d6b65da2b864822324745c0a73fe7fd86c67ccea54173682c3081d7adea8/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b", size = 75466 }, - { url = "https://files.pythonhosted.org/packages/f5/33/467a093bf004a70022cb410c590d937134bba2faa17bf9dc42a48f49af35/lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074", size = 25914 }, - { url = "https://files.pythonhosted.org/packages/77/ce/7956dc5ac2f8b62291b798c8363c81810e22a9effe469629d297d087e350/lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282", size = 27525 }, - { url = "https://files.pythonhosted.org/packages/be/11/23bcc3a85c9df7326d332b29172eaa088a3ebecb2674f257de2599e36aeb/lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a", size = 27427 }, - { url = "https://files.pythonhosted.org/packages/77/18/b78391424f3e35147b0e4d280dda0320c29ee9930b908e42fbe7920b2492/lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c", size = 66346 }, - { url = "https://files.pythonhosted.org/packages/b8/75/4669e1a7e7150e81ac27acc602ae61a37b4cc950c1ed3bd13b8d518bc026/lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255", size = 66369 }, - { url = "https://files.pythonhosted.org/packages/c8/a2/c99adb712e6ec8387d608c73d5b7a4a459c1c7813f38ee869f605bdc3f38/lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8", size = 69713 }, - { url = "https://files.pythonhosted.org/packages/f0/84/efe5dfb7c456bd3baa134dc2a4d7c891e7ce15a14c642cbfbcf50ff038ed/lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee", size = 70065 }, - { url = "https://files.pythonhosted.org/packages/36/15/f629f8aea93991a7e4700ef2601e5e4ed3fd0bcd106e3dc69c4ab12ffb98/lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4", size = 25773 }, - { url = "https://files.pythonhosted.org/packages/2b/ac/1ce4d58a508a74e4c6aa1e954e77be60720f5717c01b7b34f91ee6015837/lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696", size = 27536 }, - { url = "https://files.pythonhosted.org/packages/bc/2f/b9230d00c2eaa629e67cc69f285bf6b5692cb1d0179a1f8764edd451da86/lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94", size = 27431 }, - { url = "https://files.pythonhosted.org/packages/20/44/7d3b51ada1ddf873b136e2fa1d68bf3ee7b406b0bd9eeb97445932e2bfe1/lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b", size = 67547 }, - { url = "https://files.pythonhosted.org/packages/ab/be/d0a76dd4404ee68c7dd611c9b48e58b5c70ac5458e4c951b2c8923c24dd9/lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757", size = 67068 }, - { url = "https://files.pythonhosted.org/packages/d4/f8/d2d0d5caadf41c2d1fc9044dfc0e10d2831fb4ab6a077e68d25ea5bbff3b/lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424", size = 68587 }, - { url = "https://files.pythonhosted.org/packages/8e/ae/3e15cffacbdb64ac49930cdbc23cb0c67e1bb9e8a8ca7765fd8a8d2510c3/lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658", size = 68949 }, - { url = "https://files.pythonhosted.org/packages/36/40/471f9cbecae0ede81b61ef94687357396fe1b04d7a2090a122ad81093afe/lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70", size = 25772 }, - { url = "https://files.pythonhosted.org/packages/fe/30/40879041ed6a3364bfa862c4237aa7fe94dcd4affa2175718acbbf4d29b9/lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd", size = 27540 }, - { url = "https://files.pythonhosted.org/packages/31/8b/94dc8d58704ab87b39faed6f2fc0090b9d90e2e2aa2bbec35c79f3d2a054/lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d", size = 16405 }, -] - -[[package]] -name = "markupsafe" -version = "2.1.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206 }, - { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079 }, - { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620 }, - { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818 }, - { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493 }, - { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630 }, - { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745 }, - { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021 }, - { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659 }, - { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213 }, - { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 }, - { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 }, - { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 }, - { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 }, - { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 }, - { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 }, - { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 }, - { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 }, - { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 }, - { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 }, - { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 }, - { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 }, - { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 }, - { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 }, - { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 }, - { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 }, - { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 }, - { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 }, - { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 }, - { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 }, - { url = "https://files.pythonhosted.org/packages/f8/ff/2c942a82c35a49df5de3a630ce0a8456ac2969691b230e530ac12314364c/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", size = 18192 }, - { url = "https://files.pythonhosted.org/packages/4f/14/6f294b9c4f969d0c801a4615e221c1e084722ea6114ab2114189c5b8cbe0/MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", size = 14072 }, - { url = "https://files.pythonhosted.org/packages/81/d4/fd74714ed30a1dedd0b82427c02fa4deec64f173831ec716da11c51a50aa/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", size = 26928 }, - { url = "https://files.pythonhosted.org/packages/c7/bd/50319665ce81bb10e90d1cf76f9e1aa269ea6f7fa30ab4521f14d122a3df/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", size = 26106 }, - { url = "https://files.pythonhosted.org/packages/4c/6f/f2b0f675635b05f6afd5ea03c094557bdb8622fa8e673387444fe8d8e787/MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68", size = 25781 }, - { url = "https://files.pythonhosted.org/packages/51/e0/393467cf899b34a9d3678e78961c2c8cdf49fb902a959ba54ece01273fb1/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", size = 30518 }, - { url = "https://files.pythonhosted.org/packages/f6/02/5437e2ad33047290dafced9df741d9efc3e716b75583bbd73a9984f1b6f7/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", size = 29669 }, - { url = "https://files.pythonhosted.org/packages/0e/7d/968284145ffd9d726183ed6237c77938c021abacde4e073020f920e060b2/MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", size = 29933 }, - { url = "https://files.pythonhosted.org/packages/bf/f3/ecb00fc8ab02b7beae8699f34db9357ae49d9f21d4d3de6f305f34fa949e/MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", size = 16656 }, - { url = "https://files.pythonhosted.org/packages/92/21/357205f03514a49b293e214ac39de01fadd0970a6e05e4bf1ddd0ffd0881/MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", size = 17206 }, - { url = "https://files.pythonhosted.org/packages/0f/31/780bb297db036ba7b7bbede5e1d7f1e14d704ad4beb3ce53fb495d22bc62/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", size = 18193 }, - { url = "https://files.pythonhosted.org/packages/6c/77/d77701bbef72892affe060cdacb7a2ed7fd68dae3b477a8642f15ad3b132/MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", size = 14073 }, - { url = "https://files.pythonhosted.org/packages/d9/a7/1e558b4f78454c8a3a0199292d96159eb4d091f983bc35ef258314fe7269/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", size = 26486 }, - { url = "https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", size = 25685 }, - { url = "https://files.pythonhosted.org/packages/6a/18/ae5a258e3401f9b8312f92b028c54d7026a97ec3ab20bfaddbdfa7d8cce8/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", size = 25338 }, - { url = "https://files.pythonhosted.org/packages/0b/cc/48206bd61c5b9d0129f4d75243b156929b04c94c09041321456fd06a876d/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", size = 30439 }, - { url = "https://files.pythonhosted.org/packages/d1/06/a41c112ab9ffdeeb5f77bc3e331fdadf97fa65e52e44ba31880f4e7f983c/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", size = 29531 }, - { url = "https://files.pythonhosted.org/packages/02/8c/ab9a463301a50dab04d5472e998acbd4080597abc048166ded5c7aa768c8/MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", size = 29823 }, - { url = "https://files.pythonhosted.org/packages/bc/29/9bc18da763496b055d8e98ce476c8e718dcfd78157e17f555ce6dd7d0895/MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", size = 16658 }, - { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211 }, -] - [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, @@ -1295,178 +623,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, - { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344 }, - { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389 }, - { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607 }, - { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728 }, - { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826 }, - { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843 }, - { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219 }, - { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946 }, - { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063 }, - { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, -] - -[[package]] -name = "matplotlib" -version = "3.7.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "contourpy", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "cycler", marker = "python_full_version < '3.9'" }, - { name = "fonttools", marker = "python_full_version < '3.9'" }, - { name = "importlib-resources", version = "6.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "packaging", marker = "python_full_version < '3.9'" }, - { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pyparsing", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "python-dateutil", marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/f0/3836719cc3982fbba3b840d18a59db1d0ee9ac7986f24e8c0a092851b67b/matplotlib-3.7.5.tar.gz", hash = "sha256:1e5c971558ebc811aa07f54c7b7c677d78aa518ef4c390e14673a09e0860184a", size = 38098611 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/b0/3808e86c41e5d97822d77e89d7f3cb0890725845c050d87ec53732a8b150/matplotlib-3.7.5-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:4a87b69cb1cb20943010f63feb0b2901c17a3b435f75349fd9865713bfa63925", size = 8322924 }, - { url = "https://files.pythonhosted.org/packages/5b/05/726623be56391ba1740331ad9f1cd30e1adec61c179ddac134957a6dc2e7/matplotlib-3.7.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d3ce45010fefb028359accebb852ca0c21bd77ec0f281952831d235228f15810", size = 7438436 }, - { url = "https://files.pythonhosted.org/packages/15/83/89cdef49ef1e320060ec951ba33c132df211561d866c3ed144c81fd110b2/matplotlib-3.7.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbea1e762b28400393d71be1a02144aa16692a3c4c676ba0178ce83fc2928fdd", size = 7341849 }, - { url = "https://files.pythonhosted.org/packages/94/29/39fc4acdc296dd86e09cecb65c14966e1cf18e0f091b9cbd9bd3f0c19ee4/matplotlib-3.7.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec0e1adc0ad70ba8227e957551e25a9d2995e319c29f94a97575bb90fa1d4469", size = 11354141 }, - { url = "https://files.pythonhosted.org/packages/54/36/44c5eeb0d83ae1e3ed34d264d7adee947c4fd56c4a9464ce822de094995a/matplotlib-3.7.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6738c89a635ced486c8a20e20111d33f6398a9cbebce1ced59c211e12cd61455", size = 11457668 }, - { url = "https://files.pythonhosted.org/packages/b7/e2/f68aeaedf0ef57cbb793637ee82e62e64ea26cee908db0fe4f8e24d502c0/matplotlib-3.7.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1210b7919b4ed94b5573870f316bca26de3e3b07ffdb563e79327dc0e6bba515", size = 11580088 }, - { url = "https://files.pythonhosted.org/packages/d9/f7/7c88d34afc38943aa5e4e04d27fc9da5289a48c264c0d794f60c9cda0949/matplotlib-3.7.5-cp310-cp310-win32.whl", hash = "sha256:068ebcc59c072781d9dcdb82f0d3f1458271c2de7ca9c78f5bd672141091e9e1", size = 7339332 }, - { url = "https://files.pythonhosted.org/packages/91/99/e5f6f7c9438279581c4a2308d264fe24dc98bb80e3b2719f797227e54ddc/matplotlib-3.7.5-cp310-cp310-win_amd64.whl", hash = "sha256:f098ffbaab9df1e3ef04e5a5586a1e6b1791380698e84938d8640961c79b1fc0", size = 7506405 }, - { url = "https://files.pythonhosted.org/packages/5e/c6/45d0485e59d70b7a6a81eade5d0aed548b42cc65658c0ce0f813b9249165/matplotlib-3.7.5-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:f65342c147572673f02a4abec2d5a23ad9c3898167df9b47c149f32ce61ca078", size = 8325506 }, - { url = "https://files.pythonhosted.org/packages/0e/0a/83bd8589f3597745f624fbcc7da1140088b2f4160ca51c71553c561d0df5/matplotlib-3.7.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4ddf7fc0e0dc553891a117aa083039088d8a07686d4c93fb8a810adca68810af", size = 7439905 }, - { url = "https://files.pythonhosted.org/packages/84/c1/a7705b24f8f9b4d7ceea0002c13bae50cf9423f299f56d8c47a5cd2627d2/matplotlib-3.7.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ccb830fc29442360d91be48527809f23a5dcaee8da5f4d9b2d5b867c1b087b8", size = 7342895 }, - { url = "https://files.pythonhosted.org/packages/94/6e/55d7d8310c96a7459c883aa4be3f5a9338a108278484cbd5c95d480d1cef/matplotlib-3.7.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efc6bb28178e844d1f408dd4d6341ee8a2e906fc9e0fa3dae497da4e0cab775d", size = 11358830 }, - { url = "https://files.pythonhosted.org/packages/55/57/3b36afe104216db1cf2f3889c394b403ea87eda77c4815227c9524462ba8/matplotlib-3.7.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b15c4c2d374f249f324f46e883340d494c01768dd5287f8bc00b65b625ab56c", size = 11462575 }, - { url = "https://files.pythonhosted.org/packages/f3/0b/fabcf5f66b12fab5c4110d06a6c0fed875c7e63bc446403f58f9dadc9999/matplotlib-3.7.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d028555421912307845e59e3de328260b26d055c5dac9b182cc9783854e98fb", size = 11584280 }, - { url = "https://files.pythonhosted.org/packages/47/a9/1ad7df27a9da70b62109584632f83fe6ef45774701199c44d5777107c240/matplotlib-3.7.5-cp311-cp311-win32.whl", hash = "sha256:fe184b4625b4052fa88ef350b815559dd90cc6cc8e97b62f966e1ca84074aafa", size = 7340429 }, - { url = "https://files.pythonhosted.org/packages/e3/b1/1b6c34b89173d6c206dc5a4028e8518b4dfee3569c13bdc0c88d0486cae7/matplotlib-3.7.5-cp311-cp311-win_amd64.whl", hash = "sha256:084f1f0f2f1010868c6f1f50b4e1c6f2fb201c58475494f1e5b66fed66093647", size = 7507112 }, - { url = "https://files.pythonhosted.org/packages/75/dc/4e341a3ef36f3e7321aec0741317f12c7a23264be708a97972bf018c34af/matplotlib-3.7.5-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:34bceb9d8ddb142055ff27cd7135f539f2f01be2ce0bafbace4117abe58f8fe4", size = 8323797 }, - { url = "https://files.pythonhosted.org/packages/af/83/bbb482d678362ceb68cc59ec4fc705dde636025969361dac77be868541ef/matplotlib-3.7.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c5a2134162273eb8cdfd320ae907bf84d171de948e62180fa372a3ca7cf0f433", size = 7439549 }, - { url = "https://files.pythonhosted.org/packages/1a/ee/e49a92d9e369b2b9e4373894171cb4e641771cd7f81bde1d8b6fb8c60842/matplotlib-3.7.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:039ad54683a814002ff37bf7981aa1faa40b91f4ff84149beb53d1eb64617980", size = 7341788 }, - { url = "https://files.pythonhosted.org/packages/48/79/89cb2fc5ddcfc3d440a739df04dbe6e4e72b1153d1ebd32b45d42eb71d27/matplotlib-3.7.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d742ccd1b09e863b4ca58291728db645b51dab343eebb08d5d4b31b308296ce", size = 11356329 }, - { url = "https://files.pythonhosted.org/packages/ff/25/84f181cdae5c9eba6fd1c2c35642aec47233425fe3b0d6fccdb323fb36e0/matplotlib-3.7.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:743b1c488ca6a2bc7f56079d282e44d236bf375968bfd1b7ba701fd4d0fa32d6", size = 11577813 }, - { url = "https://files.pythonhosted.org/packages/9f/24/b2db065d40e58033b3350222fb8bbb0ffcb834029df9c1f9349dd9c7dd45/matplotlib-3.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:fbf730fca3e1f23713bc1fae0a57db386e39dc81ea57dc305c67f628c1d7a342", size = 7507667 }, - { url = "https://files.pythonhosted.org/packages/e3/72/50a38c8fd5dc845b06f8e71c9da802db44b81baabf4af8be78bb8a5622ea/matplotlib-3.7.5-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:cfff9b838531698ee40e40ea1a8a9dc2c01edb400b27d38de6ba44c1f9a8e3d2", size = 8322659 }, - { url = "https://files.pythonhosted.org/packages/b1/ea/129163dcd21db6da5d559a8160c4a74c1dc5f96ac246a3d4248b43c7648d/matplotlib-3.7.5-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:1dbcca4508bca7847fe2d64a05b237a3dcaec1f959aedb756d5b1c67b770c5ee", size = 7438408 }, - { url = "https://files.pythonhosted.org/packages/aa/59/4d13e5b6298b1ca5525eea8c68d3806ae93ab6d0bb17ca9846aa3156b92b/matplotlib-3.7.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4cdf4ef46c2a1609a50411b66940b31778db1e4b73d4ecc2eaa40bd588979b13", size = 7341782 }, - { url = "https://files.pythonhosted.org/packages/9e/c4/f562df04b08487731743511ff274ae5d31dce2ff3e5621f8b070d20ab54a/matplotlib-3.7.5-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:167200ccfefd1674b60e957186dfd9baf58b324562ad1a28e5d0a6b3bea77905", size = 9196487 }, - { url = "https://files.pythonhosted.org/packages/30/33/cc27211d2ffeee4fd7402dca137b6e8a83f6dcae3d4be8d0ad5068555561/matplotlib-3.7.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:53e64522934df6e1818b25fd48cf3b645b11740d78e6ef765fbb5fa5ce080d02", size = 9213051 }, - { url = "https://files.pythonhosted.org/packages/9b/9d/8bd37c86b79312c9dbcfa379dec32303f9b38e8456e0829d7e666a0e0a05/matplotlib-3.7.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e3bc79b2d7d615067bd010caff9243ead1fc95cf735c16e4b2583173f717eb", size = 11370807 }, - { url = "https://files.pythonhosted.org/packages/c0/1e/b24a07a849c8d458f1b3724f49029f0dedf748bdedb4d5f69491314838b6/matplotlib-3.7.5-cp38-cp38-win32.whl", hash = "sha256:6b641b48c6819726ed47c55835cdd330e53747d4efff574109fd79b2d8a13748", size = 7340461 }, - { url = "https://files.pythonhosted.org/packages/16/51/58b0b9de42fe1e665736d9286f88b5f1556a0e22bed8a71f468231761083/matplotlib-3.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:f0b60993ed3488b4532ec6b697059897891927cbfc2b8d458a891b60ec03d9d7", size = 7507471 }, - { url = "https://files.pythonhosted.org/packages/0d/00/17487e9e8949ca623af87f6c8767408efe7530b7e1f4d6897fa7fa940834/matplotlib-3.7.5-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:090964d0afaff9c90e4d8de7836757e72ecfb252fb02884016d809239f715651", size = 8323175 }, - { url = "https://files.pythonhosted.org/packages/6a/84/be0acd521fa9d6697657cf35878153f8009a42b4b75237aebc302559a8a9/matplotlib-3.7.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9fc6fcfbc55cd719bc0bfa60bde248eb68cf43876d4c22864603bdd23962ba25", size = 7438737 }, - { url = "https://files.pythonhosted.org/packages/17/39/175f36a6d68d0cf47a4fecbae9728048355df23c9feca8688f1476b198e6/matplotlib-3.7.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7cc3078b019bb863752b8b60e8b269423000f1603cb2299608231996bd9d54", size = 7341916 }, - { url = "https://files.pythonhosted.org/packages/36/c0/9a1c2a79f85c15d41b60877cbc333694ed80605e5c97a33880c4ecfd5bf1/matplotlib-3.7.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e4e9a868e8163abaaa8259842d85f949a919e1ead17644fb77a60427c90473c", size = 11352264 }, - { url = "https://files.pythonhosted.org/packages/a6/39/b0204e0e7a899b0676733366a55ccafa723799b719bc7f2e85e5ecde26a0/matplotlib-3.7.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa7ebc995a7d747dacf0a717d0eb3aa0f0c6a0e9ea88b0194d3a3cd241a1500f", size = 11454722 }, - { url = "https://files.pythonhosted.org/packages/d8/39/64dd1d36c79e72e614977db338d180cf204cf658927c05a8ef2d47feb4c0/matplotlib-3.7.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3785bfd83b05fc0e0c2ae4c4a90034fe693ef96c679634756c50fe6efcc09856", size = 11576343 }, - { url = "https://files.pythonhosted.org/packages/31/b4/e77bc11394d858bdf15e356980fceb4ac9604b0fa8212ef3ca4f1dc166b8/matplotlib-3.7.5-cp39-cp39-win32.whl", hash = "sha256:29b058738c104d0ca8806395f1c9089dfe4d4f0f78ea765c6c704469f3fffc81", size = 7340455 }, - { url = "https://files.pythonhosted.org/packages/4a/84/081820c596b9555ecffc6819ee71f847f2fbb0d7c70a42c1eeaa54edf3e0/matplotlib-3.7.5-cp39-cp39-win_amd64.whl", hash = "sha256:fd4028d570fa4b31b7b165d4a685942ae9cdc669f33741e388c01857d9723eab", size = 7507711 }, - { url = "https://files.pythonhosted.org/packages/27/6c/1bb10f3d6f337b9faa2e96a251bd87ba5fed85a608df95eb4d69acc109f0/matplotlib-3.7.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2a9a3f4d6a7f88a62a6a18c7e6a84aedcaf4faf0708b4ca46d87b19f1b526f88", size = 7397285 }, - { url = "https://files.pythonhosted.org/packages/b2/36/66cfea213e9ba91cda9e257542c249ed235d49021af71c2e8007107d7d4c/matplotlib-3.7.5-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b3fd853d4a7f008a938df909b96db0b454225f935d3917520305b90680579c", size = 7552612 }, - { url = "https://files.pythonhosted.org/packages/77/df/16655199bf984c37c6a816b854bc032b56aef521aadc04f27928422f3c91/matplotlib-3.7.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ad550da9f160737d7890217c5eeed4337d07e83ca1b2ca6535078f354e7675", size = 7515564 }, - { url = "https://files.pythonhosted.org/packages/5b/c8/3534c3705a677b71abb6be33609ba129fdeae2ea4e76b2fd3ab62c86fab3/matplotlib-3.7.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:20da7924a08306a861b3f2d1da0d1aa9a6678e480cf8eacffe18b565af2813e7", size = 7521336 }, - { url = "https://files.pythonhosted.org/packages/20/a0/c5c0d410798b387ed3a177a5a7eba21055dd9c41d4b15bd0861241a5a60e/matplotlib-3.7.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b45c9798ea6bb920cb77eb7306409756a7fab9db9b463e462618e0559aecb30e", size = 7397931 }, - { url = "https://files.pythonhosted.org/packages/c3/2f/9e9509727d4c7d1b8e2c88e9330a97d54a1dd20bd316a0c8d2f8b38c4513/matplotlib-3.7.5-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a99866267da1e561c7776fe12bf4442174b79aac1a47bd7e627c7e4d077ebd83", size = 7553224 }, - { url = "https://files.pythonhosted.org/packages/89/0c/5f3e403dcf5c23799c92b0139dd00e41caf23983e9281f5bfeba3065e7d2/matplotlib-3.7.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6aa62adb6c268fc87d80f963aca39c64615c31830b02697743c95590ce3fbb", size = 7513250 }, - { url = "https://files.pythonhosted.org/packages/87/e0/03eba0a8c3775ef910dbb3a287114a64c47abbcaeab2543c59957f155a86/matplotlib-3.7.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e530ab6a0afd082d2e9c17eb1eb064a63c5b09bb607b2b74fa41adbe3e162286", size = 7521729 }, -] - -[[package]] -name = "matplotlib" -version = "3.9.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "cycler", marker = "python_full_version == '3.9.*'" }, - { name = "fonttools", marker = "python_full_version == '3.9.*'" }, - { name = "importlib-resources", version = "6.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "packaging", marker = "python_full_version == '3.9.*'" }, - { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "pyparsing", version = "3.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "python-dateutil", marker = "python_full_version == '3.9.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/17/1747b4154034befd0ed33b52538f5eb7752d05bb51c5e2a31470c3bc7d52/matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3", size = 36106529 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/4f/8c/b1f5bd2bd70e60f93b1b54c4d5ba7a992312021d0ddddf572f9a1a6d9348/matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d", size = 7828211 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/23/e1/4f0e237bf349c02ff9d1b6e7109f1a17f745263809b9714a8576dc17752b/matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3", size = 7838065 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/9f/8b/3d0c7a002db3b1ed702731c2a9a06d78d035f1f2fb0fb936a8e43cc1e9f4/matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b", size = 7841160 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/71/1f/c6e1daea55b7bfeb3d84c6cb1abc449f6a02b181e7e2a5e4db34c3afb793/matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799", size = 7841311 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/10/11/237f9c3a4e8d810b1759b67ff2da7c32c04f9c80aa475e7beb36ed43a8fb/matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965", size = 7896987 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/57/68/c2feb4667adbf882ffa4b3e0ac9967f848980d9f8b5bebd86644aa67ce6a/matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac", size = 7822962 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/5e/b6/5a1f868782cd13f053a679984e222007ecff654a9bfbac6b27a65f4eeb05/matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865", size = 7854624 }, ] [[package]] name = "matplotlib" version = "3.10.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "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 = "cycler", marker = "python_full_version >= '3.10'" }, - { name = "fonttools", marker = "python_full_version >= '3.10'" }, - { name = "kiwisolver", version = "1.4.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pyparsing", version = "3.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/b1/f70e27cf1cd76ce2a5e1aa5579d05afe3236052c6d9b9a96325bc823a17e/matplotlib-3.10.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff2ae14910be903f4a24afdbb6d7d3a6c44da210fc7d42790b87aeac92238a16", size = 8163654 }, - { url = "https://files.pythonhosted.org/packages/26/af/5ec3d4636106718bb62503a03297125d4514f98fe818461bd9e6b9d116e4/matplotlib-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0721a3fd3d5756ed593220a8b86808a36c5031fce489adb5b31ee6dbb47dd5b2", size = 8037943 }, - { url = "https://files.pythonhosted.org/packages/a1/3d/07f9003a71b698b848c9925d05979ffa94a75cd25d1a587202f0bb58aa81/matplotlib-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0673b4b8f131890eb3a1ad058d6e065fb3c6e71f160089b65f8515373394698", size = 8449510 }, - { url = "https://files.pythonhosted.org/packages/12/87/9472d4513ff83b7cd864311821793ab72234fa201ab77310ec1b585d27e2/matplotlib-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e875b95ac59a7908978fe307ecdbdd9a26af7fa0f33f474a27fcf8c99f64a19", size = 8586585 }, - { url = "https://files.pythonhosted.org/packages/31/9e/fe74d237d2963adae8608faeb21f778cf246dbbf4746cef87cffbc82c4b6/matplotlib-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2589659ea30726284c6c91037216f64a506a9822f8e50592d48ac16a2f29e044", size = 9397911 }, - { url = "https://files.pythonhosted.org/packages/b6/1b/025d3e59e8a4281ab463162ad7d072575354a1916aba81b6a11507dfc524/matplotlib-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a97ff127f295817bc34517255c9db6e71de8eddaab7f837b7d341dee9f2f587f", size = 8052998 }, { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669 }, { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996 }, { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612 }, @@ -1491,18 +666,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864 }, { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487 }, { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832 }, - { url = "https://files.pythonhosted.org/packages/c8/f6/10adb696d8cbeed2ab4c2e26ecf1c80dd3847bbf3891f4a0c362e0e08a5a/matplotlib-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:648406f1899f9a818cef8c0231b44dcfc4ff36f167101c3fd1c9151f24220fdc", size = 8158685 }, - { url = "https://files.pythonhosted.org/packages/3f/84/0603d917406072763e7f9bb37747d3d74d7ecd4b943a8c947cc3ae1cf7af/matplotlib-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:02582304e352f40520727984a5a18f37e8187861f954fea9be7ef06569cf85b4", size = 8035491 }, - { url = "https://files.pythonhosted.org/packages/fd/7d/6a8b31dd07ed856b3eae001c9129670ef75c4698fa1c2a6ac9f00a4a7054/matplotlib-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3809916157ba871bcdd33d3493acd7fe3037db5daa917ca6e77975a94cef779", size = 8590087 }, ] [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/18/fa675aa501e11d6d6ca0ae73a101b2f3571a565e0f7d38e062eec18a91ee/mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f", size = 8612 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", size = 8556 }, + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, ] [[package]] @@ -1510,67 +682,11 @@ name = "metrohash-python" version = "1.1.3.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/95/b5/4002d16e71d6a4b1c14a37d204e25aba6508b12915fe0dd07fa703ca0fa1/metrohash-python-1.1.3.3.tar.gz", hash = "sha256:2003c9b2d07514c8228d901e3b32492ae42459e577e946ab91c0ef6eddfd0926", size = 45994 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/07/aa99c165850f2250cfb2c1311abe36ade678ad2c4f7b02339068fc5f940a/metrohash_python-1.1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1a5076239cf18057c92c0c8c14164a2863cfeaf8372e8af98b238c730137801", size = 27447 }, - { url = "https://files.pythonhosted.org/packages/04/77/7efaa8d0e9219b448af1865e2140fe583032f836284f1846fc950744e909/metrohash_python-1.1.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b8cbef4169a152d9fdffee4145aa2f30f38ebc00e6e01fdb58a550e7b67bf31", size = 147878 }, - { url = "https://files.pythonhosted.org/packages/4f/85/b2e3a06b4f6b6093a95117072f16ccee0e5001139fd988a7b9e840a5f5bc/metrohash_python-1.1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2214b48d8e7663223a358951df4b0ce98493be660face2536a146957eb80674c", size = 149168 }, - { url = "https://files.pythonhosted.org/packages/a8/b0/da7928dce6ecc35aeff3f16a13a8d5a5924ccbdaeb911a5f8c162c5bfd89/metrohash_python-1.1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a6e9ea7d87ff48d0021fe88666f399b3173fa4e9545e4de1120d8a72aa185711", size = 752520 }, - { url = "https://files.pythonhosted.org/packages/ba/c6/f88a22c6c788457965e486e5dcb2b0f775de1d5a210a6d863b63ff4b47f6/metrohash_python-1.1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d70fb42aae512538c91d43a1c2edcea875c80919a78ce8e22fedd07a3109f1d1", size = 694849 }, - { url = "https://files.pythonhosted.org/packages/81/80/65cde9cefd3585985a536666396f1f9b9bdffd3bcc3f4266ccfd8e01e38f/metrohash_python-1.1.3.3-cp310-cp310-win32.whl", hash = "sha256:a2def24d47f0f226c4278dd30f51498a57199c64cc8432989d67ef11b4e518aa", size = 24596 }, - { url = "https://files.pythonhosted.org/packages/85/9f/3747be69c137cea8c547691e4eb8f1da84b72976d743c4f688c1174ede63/metrohash_python-1.1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:0bc6a8429ff017f2d8cb6ffb76d5355f9c6788010667470ab7a1b2b10252d60a", size = 24493 }, - { url = "https://files.pythonhosted.org/packages/2a/12/81b8a717dc01925dfa7eaa3f8e71c879b14713d32ea7998860d16b3b316f/metrohash_python-1.1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ff513c7eddca391a02f23fafb0dfbf6aef80102f19db5d2abda3aee7534488c", size = 26952 }, - { url = "https://files.pythonhosted.org/packages/96/27/cf7d9ac0c25868176799bc3a8298ef7d51dddd93799404a3413439e5579b/metrohash_python-1.1.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f2ae11b283f92f1324fb676a0ba8a7c4b4fa48e30678e0b04ebea49d40cbb7", size = 146966 }, - { url = "https://files.pythonhosted.org/packages/88/39/03274c4508fca778003f689ef89b68a862c8f0f5f8d8ee829ad88405d704/metrohash_python-1.1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbd744f62301ca716891c8bdd546c0fe951f59eb36c0ba03f2c0ccbc5ea5ca04", size = 148769 }, - { url = "https://files.pythonhosted.org/packages/c2/f1/65f44a55e4acbbd7929676c54874af061284feb13669b0d49b5917868397/metrohash_python-1.1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:393521a47116964ba37935503c1fb04d596c608f32123b12317148618659bb6f", size = 753590 }, - { url = "https://files.pythonhosted.org/packages/89/28/2cdd4874fd857d1b676baa56a352ff30364a60d739c5471f954a0cb541d5/metrohash_python-1.1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:292daf3debc95787cc2cab881ee97079fe94bdc31de89ef7af44b115bdee2fa9", size = 695716 }, - { url = "https://files.pythonhosted.org/packages/7e/56/7b9a7eb84d83c8b2d146e8c9d486153136cd4ab17fb19485f02b042d53eb/metrohash_python-1.1.3.3-cp38-cp38-win32.whl", hash = "sha256:5ff83cd43f2e2cec81d0026295a2320aa11a176c859e058a618b7fb08065c467", size = 24635 }, - { url = "https://files.pythonhosted.org/packages/64/c8/d700755c2afb6c0ac6e0d1204887ccdcda09efe9655c5270b79ab70391d2/metrohash_python-1.1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:a080987405c1cff83fd4449a753724be630e69622f181c24157ad52fb4a20115", size = 24533 }, - { url = "https://files.pythonhosted.org/packages/8e/32/968149f9bf2a98d1e1a4058547e8b0fc7dc0d7f752765ce6552cf68d35fc/metrohash_python-1.1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5070a1fe73649adc19d3604ed897d520c25b858a671a5661e0a669950521a0d4", size = 27448 }, - { url = "https://files.pythonhosted.org/packages/37/31/5f0b7b68acdeb2b0aa433c89f59e4d10d8e64557b7cb551ad27f01d68ccb/metrohash_python-1.1.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0b6f00066d92c7350521e4c42770afef0929507f002aa1c213b76b58318ac30", size = 146035 }, - { url = "https://files.pythonhosted.org/packages/07/7c/1103bc8046ced08db78a09c28c36efa35f060e2e5a00df3d0a34c3bd310e/metrohash_python-1.1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c5243c13a542586f673ae17a0a2dd04e2c11a55b65dd6a5e391617259f7f1c7", size = 147862 }, - { url = "https://files.pythonhosted.org/packages/8e/7f/2608f6c2aecec7c6271bfffd2241a8ba89508d4cf37ef10686bafe2ea0b5/metrohash_python-1.1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ec25885e7c74d668cef349538bd52250a7ec543f08f4e4c940db1f4b4c657c09", size = 751025 }, - { url = "https://files.pythonhosted.org/packages/62/c0/7bf43ca88f08843182465eee9b2305ca9efe6fe3adad738a8fe3b99d6f0a/metrohash_python-1.1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0e50c9c80a65b29990cf3c44af8b758c79479c66465213cbf924e2b0fce0ebe7", size = 693242 }, - { url = "https://files.pythonhosted.org/packages/01/c3/25c8628ce452840ad9f068172f365477e330f63e05e55d4d13c278805146/metrohash_python-1.1.3.3-cp39-cp39-win32.whl", hash = "sha256:6a99e89c5e0f16e01b07cdac19146ea0e86ff104ff49a3c2bbb80de3f317d3f9", size = 24560 }, - { url = "https://files.pythonhosted.org/packages/f4/18/ca01799efbed0c638af9b3956996c6ae9d135230bbf4358d486d8d2a4d18/metrohash_python-1.1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:8e4bb9e332fb397d5fabaee9ba84c37765abe50c7f633d126a8725711b3b0330", size = 24503 }, - { url = "https://files.pythonhosted.org/packages/ca/74/bbaef04e718acad2e8846bbdb112733915a368202546ef35ee2030de4c88/metrohash_python-1.1.3.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:db7e9c068c641287b38b7555c3c3a27d1ce57f1042bc0013ee36c683ae6221a8", size = 45863 }, - { url = "https://files.pythonhosted.org/packages/92/ae/ddd182b33e11b51577811ab855fd0423307dbbe8ac10390ada8295147a8e/metrohash_python-1.1.3.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e78077c597626cd6e90e3445b9dfa5d04ca7a064edb77ff15f0e808d30477c1c", size = 172194 }, - { url = "https://files.pythonhosted.org/packages/73/57/9b8e2273e01d229715bf0d7b8cd0a62273438e4a7b0463fb431db289cb55/metrohash_python-1.1.3.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64c98f0a76a92fe2e20118dfe490ec58a0080ad181e18d6e1e6d2c523d9b2cb6", size = 177579 }, - { url = "https://files.pythonhosted.org/packages/fd/23/a5e7d343ea99c5eb1883f2f0440cdb1d4f03c919c589352163dbfbc9afd1/metrohash_python-1.1.3.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1b986b902dd424fe69981ceafcc776ba52db61a13430ebf7bc7ccb76771ae84f", size = 42845 }, -] - -[[package]] -name = "networkx" -version = "3.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/47b974da1a73f063c158a1f4cc33ed0abf7c04f98a19050e80c533c31f0c/networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61", size = 2021691 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/05/9d4f9b78ead6b2661d6e8ea772e111fc4a9fbd866ad0c81906c11206b55e/networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36", size = 2072251 }, -] - -[[package]] -name = "networkx" -version = "3.2.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772 }, -] [[package]] name = "networkx" version = "3.4.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "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 } wheels = [ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, @@ -1578,89 +694,50 @@ wheels = [ [[package]] name = "numpy" -version = "1.24.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", size = 10911229 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", size = 19789140 }, - { url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", size = 13854297 }, - { url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", size = 13995611 }, - { url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", size = 17282357 }, - { url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", size = 12429222 }, - { url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", size = 14841514 }, - { url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", size = 19775508 }, - { url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", size = 13840033 }, - { url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", size = 13991951 }, - { url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", size = 17278923 }, - { url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", size = 12422446 }, - { url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", size = 14834466 }, - { url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", size = 19780722 }, - { url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", size = 13843102 }, - { url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", size = 14039616 }, - { url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", size = 17316263 }, - { url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", size = 12455660 }, - { url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", size = 14868112 }, - { url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", size = 19816549 }, - { url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", size = 13879950 }, - { url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9", size = 14030228 }, - { url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", size = 17311170 }, - { url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", size = 12454918 }, - { url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", size = 14867441 }, - { url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", size = 19156590 }, - { url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", size = 16705744 }, - { url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", size = 14734290 }, -] - -[[package]] -name = "numpy" -version = "1.26.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, - { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, - { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, - { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, - { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, - { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, - { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, - { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, - { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, - { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, - { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, - { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, - { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, - { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, - { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, - { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, - { url = "https://files.pythonhosted.org/packages/7d/24/ce71dc08f06534269f66e73c04f5709ee024a1afe92a7b6e1d73f158e1f8/numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", size = 20636301 }, - { url = "https://files.pythonhosted.org/packages/ae/8c/ab03a7c25741f9ebc92684a20125fbc9fc1b8e1e700beb9197d750fdff88/numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", size = 13971216 }, - { url = "https://files.pythonhosted.org/packages/6d/64/c3bcdf822269421d85fe0d64ba972003f9bb4aa9a419da64b86856c9961f/numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", size = 14226281 }, - { url = "https://files.pythonhosted.org/packages/54/30/c2a907b9443cf42b90c17ad10c1e8fa801975f01cb9764f3f8eb8aea638b/numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", size = 18249516 }, - { url = "https://files.pythonhosted.org/packages/43/12/01a563fc44c07095996d0129b8899daf89e4742146f7044cdbdb3101c57f/numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", size = 13882132 }, - { url = "https://files.pythonhosted.org/packages/16/ee/9df80b06680aaa23fc6c31211387e0db349e0e36d6a63ba3bd78c5acdf11/numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", size = 18084181 }, - { url = "https://files.pythonhosted.org/packages/28/7d/4b92e2fe20b214ffca36107f1a3e75ef4c488430e64de2d9af5db3a4637d/numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", size = 5976360 }, - { url = "https://files.pythonhosted.org/packages/b5/42/054082bd8220bbf6f297f982f0a8f5479fcbc55c8b511d928df07b965869/numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", size = 15814633 }, - { url = "https://files.pythonhosted.org/packages/3f/72/3df6c1c06fc83d9cfe381cccb4be2532bbd38bf93fbc9fad087b6687f1c0/numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", size = 20455961 }, - { url = "https://files.pythonhosted.org/packages/8e/02/570545bac308b58ffb21adda0f4e220ba716fb658a63c151daecc3293350/numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", size = 18061071 }, - { url = "https://files.pythonhosted.org/packages/f4/5f/fafd8c51235f60d49f7a88e2275e13971e90555b67da52dd6416caec32fe/numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", size = 15709730 }, +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989 }, + { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910 }, + { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490 }, + { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754 }, + { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 }, + { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 }, + { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 }, + { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 }, + { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 }, + { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, + { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, + { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, + { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, + { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, + { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, + { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, + { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, + { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, + { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, ] [[package]] @@ -1672,119 +749,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] -[[package]] -name = "pillow" -version = "10.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271 }, - { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658 }, - { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075 }, - { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808 }, - { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290 }, - { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163 }, - { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100 }, - { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880 }, - { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218 }, - { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487 }, - { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219 }, - { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265 }, - { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655 }, - { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304 }, - { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804 }, - { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126 }, - { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541 }, - { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616 }, - { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802 }, - { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213 }, - { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498 }, - { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219 }, - { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350 }, - { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980 }, - { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799 }, - { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973 }, - { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054 }, - { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484 }, - { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375 }, - { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773 }, - { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690 }, - { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951 }, - { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427 }, - { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 }, - { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 }, - { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 }, - { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 }, - { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 }, - { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 }, - { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 }, - { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 }, - { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 }, - { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 }, - { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 }, - { url = "https://files.pythonhosted.org/packages/56/70/f40009702a477ce87d8d9faaa4de51d6562b3445d7a314accd06e4ffb01d/pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", size = 3509213 }, - { url = "https://files.pythonhosted.org/packages/10/43/105823d233c5e5d31cea13428f4474ded9d961652307800979a59d6a4276/pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", size = 3375883 }, - { url = "https://files.pythonhosted.org/packages/3c/ad/7850c10bac468a20c918f6a5dbba9ecd106ea1cdc5db3c35e33a60570408/pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", size = 4330810 }, - { url = "https://files.pythonhosted.org/packages/84/4c/69bbed9e436ac22f9ed193a2b64f64d68fcfbc9f4106249dc7ed4889907b/pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", size = 4444341 }, - { url = "https://files.pythonhosted.org/packages/8f/4f/c183c63828a3f37bf09644ce94cbf72d4929b033b109160a5379c2885932/pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", size = 4356005 }, - { url = "https://files.pythonhosted.org/packages/fb/ad/435fe29865f98a8fbdc64add8875a6e4f8c97749a93577a8919ec6f32c64/pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", size = 4525201 }, - { url = "https://files.pythonhosted.org/packages/80/74/be8bf8acdfd70e91f905a12ae13cfb2e17c0f1da745c40141e26d0971ff5/pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", size = 4460635 }, - { url = "https://files.pythonhosted.org/packages/e4/90/763616e66dc9ad59c9b7fb58f863755e7934ef122e52349f62c7742b82d3/pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", size = 4590283 }, - { url = "https://files.pythonhosted.org/packages/69/66/03002cb5b2c27bb519cba63b9f9aa3709c6f7a5d3b285406c01f03fb77e5/pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", size = 2235185 }, - { url = "https://files.pythonhosted.org/packages/f2/75/3cb820b2812405fc7feb3d0deb701ef0c3de93dc02597115e00704591bc9/pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", size = 2554594 }, - { url = "https://files.pythonhosted.org/packages/31/85/955fa5400fa8039921f630372cfe5056eed6e1b8e0430ee4507d7de48832/pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", size = 3509283 }, - { url = "https://files.pythonhosted.org/packages/23/9c/343827267eb28d41cd82b4180d33b10d868af9077abcec0af9793aa77d2d/pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", size = 3375691 }, - { url = "https://files.pythonhosted.org/packages/60/a3/7ebbeabcd341eab722896d1a5b59a3df98c4b4d26cf4b0385f8aa94296f7/pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", size = 4328295 }, - { url = "https://files.pythonhosted.org/packages/32/3f/c02268d0c6fb6b3958bdda673c17b315c821d97df29ae6969f20fb49388a/pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", size = 4440810 }, - { url = "https://files.pythonhosted.org/packages/67/5d/1c93c8cc35f2fdd3d6cc7e4ad72d203902859a2867de6ad957d9b708eb8d/pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", size = 4352283 }, - { url = "https://files.pythonhosted.org/packages/bc/a8/8655557c9c7202b8abbd001f61ff36711cefaf750debcaa1c24d154ef602/pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", size = 4521800 }, - { url = "https://files.pythonhosted.org/packages/58/78/6f95797af64d137124f68af1bdaa13b5332da282b86031f6fa70cf368261/pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", size = 4459177 }, - { url = "https://files.pythonhosted.org/packages/8a/6d/2b3ce34f1c4266d79a78c9a51d1289a33c3c02833fe294ef0dcbb9cba4ed/pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", size = 4589079 }, - { url = "https://files.pythonhosted.org/packages/e3/e0/456258c74da1ff5bf8ef1eab06a95ca994d8b9ed44c01d45c3f8cbd1db7e/pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", size = 2235247 }, - { url = "https://files.pythonhosted.org/packages/37/f8/bef952bdb32aa53741f58bf21798642209e994edc3f6598f337f23d5400a/pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", size = 2554479 }, - { url = "https://files.pythonhosted.org/packages/bb/8e/805201619cad6651eef5fc1fdef913804baf00053461522fabbc5588ea12/pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", size = 2243226 }, - { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889 }, - { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160 }, - { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020 }, - { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539 }, - { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125 }, - { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373 }, - { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661 }, - { url = "https://files.pythonhosted.org/packages/e1/1f/5a9fcd6ced51633c22481417e11b1b47d723f64fb536dfd67c015eb7f0ab/pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", size = 3493850 }, - { url = "https://files.pythonhosted.org/packages/cb/e6/3ea4755ed5320cb62aa6be2f6de47b058c6550f752dd050e86f694c59798/pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", size = 3346118 }, - { url = "https://files.pythonhosted.org/packages/0a/22/492f9f61e4648422b6ca39268ec8139277a5b34648d28f400faac14e0f48/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", size = 3434958 }, - { url = "https://files.pythonhosted.org/packages/f9/19/559a48ad4045704bb0547965b9a9345f5cd461347d977a56d178db28819e/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", size = 3490340 }, - { url = "https://files.pythonhosted.org/packages/d9/de/cebaca6fb79905b3a1aa0281d238769df3fb2ede34fd7c0caa286575915a/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", size = 3476048 }, - { url = "https://files.pythonhosted.org/packages/71/f0/86d5b2f04693b0116a01d75302b0a307800a90d6c351a8aa4f8ae76cd499/pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", size = 3579366 }, - { url = "https://files.pythonhosted.org/packages/37/ae/2dbfc38cc4fd14aceea14bc440d5151b21f64c4c3ba3f6f4191610b7ee5d/pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", size = 2554652 }, -] - [[package]] name = "pillow" version = "11.2.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/8b/b158ad57ed44d3cc54db8d68ad7c0a58b8fc0e4c7a3f995f9d62d5b464a1/pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047", size = 3198442 }, - { url = "https://files.pythonhosted.org/packages/b1/f8/bb5d956142f86c2d6cc36704943fa761f2d2e4c48b7436fd0a85c20f1713/pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95", size = 3030553 }, - { url = "https://files.pythonhosted.org/packages/22/7f/0e413bb3e2aa797b9ca2c5c38cb2e2e45d88654e5b12da91ad446964cfae/pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61", size = 4405503 }, - { url = "https://files.pythonhosted.org/packages/f3/b4/cc647f4d13f3eb837d3065824aa58b9bcf10821f029dc79955ee43f793bd/pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1", size = 4490648 }, - { url = "https://files.pythonhosted.org/packages/c2/6f/240b772a3b35cdd7384166461567aa6713799b4e78d180c555bd284844ea/pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c", size = 4508937 }, - { url = "https://files.pythonhosted.org/packages/f3/5e/7ca9c815ade5fdca18853db86d812f2f188212792780208bdb37a0a6aef4/pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d", size = 4599802 }, - { url = "https://files.pythonhosted.org/packages/02/81/c3d9d38ce0c4878a77245d4cf2c46d45a4ad0f93000227910a46caff52f3/pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97", size = 4576717 }, - { url = "https://files.pythonhosted.org/packages/42/49/52b719b89ac7da3185b8d29c94d0e6aec8140059e3d8adcaa46da3751180/pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579", size = 4654874 }, - { url = "https://files.pythonhosted.org/packages/5b/0b/ede75063ba6023798267023dc0d0401f13695d228194d2242d5a7ba2f964/pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d", size = 2331717 }, - { url = "https://files.pythonhosted.org/packages/ed/3c/9831da3edea527c2ed9a09f31a2c04e77cd705847f13b69ca60269eec370/pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad", size = 2676204 }, - { url = "https://files.pythonhosted.org/packages/01/97/1f66ff8a1503d8cbfc5bae4dc99d54c6ec1e22ad2b946241365320caabc2/pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2", size = 2414767 }, { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450 }, { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550 }, { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018 }, @@ -1829,24 +799,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, - { url = "https://files.pythonhosted.org/packages/21/3a/c1835d1c7cf83559e95b4f4ed07ab0bb7acc689712adfce406b3f456e9fd/pillow-11.2.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7491cf8a79b8eb867d419648fff2f83cb0b3891c8b36da92cc7f1931d46108c8", size = 3198391 }, - { url = "https://files.pythonhosted.org/packages/b6/4d/dcb7a9af3fc1e8653267c38ed622605d9d1793349274b3ef7af06457e257/pillow-11.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b02d8f9cb83c52578a0b4beadba92e37d83a4ef11570a8688bbf43f4ca50909", size = 3030573 }, - { url = "https://files.pythonhosted.org/packages/9d/29/530ca098c1a1eb31d4e163d317d0e24e6d2ead907991c69ca5b663de1bc5/pillow-11.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:014ca0050c85003620526b0ac1ac53f56fc93af128f7546623cc8e31875ab928", size = 4398677 }, - { url = "https://files.pythonhosted.org/packages/8b/ee/0e5e51db34de1690264e5f30dcd25328c540aa11d50a3bc0b540e2a445b6/pillow-11.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3692b68c87096ac6308296d96354eddd25f98740c9d2ab54e1549d6c8aea9d79", size = 4484986 }, - { url = "https://files.pythonhosted.org/packages/93/7d/bc723b41ce3d2c28532c47678ec988974f731b5c6fadd5b3a4fba9015e4f/pillow-11.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f781dcb0bc9929adc77bad571b8621ecb1e4cdef86e940fe2e5b5ee24fd33b35", size = 4501897 }, - { url = "https://files.pythonhosted.org/packages/be/0b/532e31abc7389617ddff12551af625a9b03cd61d2989fa595e43c470ec67/pillow-11.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2b490402c96f907a166615e9a5afacf2519e28295f157ec3a2bb9bd57de638cb", size = 4592618 }, - { url = "https://files.pythonhosted.org/packages/4c/f0/21ed6499a6216fef753e2e2254a19d08bff3747108ba042422383f3e9faa/pillow-11.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6b20b93b3ccc9c1b597999209e4bc5cf2853f9ee66e3fc9a400a78733ffc9a", size = 4570493 }, - { url = "https://files.pythonhosted.org/packages/68/de/17004ddb8ab855573fe1127ab0168d11378cdfe4a7ee2a792a70ff2e9ba7/pillow-11.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b835d89c08a6c2ee7781b8dd0a30209a8012b5f09c0a665b65b0eb3560b6f36", size = 4647748 }, - { url = "https://files.pythonhosted.org/packages/c7/23/82ecb486384bb3578115c509d4a00bb52f463ee700a5ca1be53da3c88c19/pillow-11.2.1-cp39-cp39-win32.whl", hash = "sha256:b10428b3416d4f9c61f94b494681280be7686bda15898a3a9e08eb66a6d92d67", size = 2331731 }, - { url = "https://files.pythonhosted.org/packages/58/bb/87efd58b3689537a623d44dbb2550ef0bb5ff6a62769707a0fe8b1a7bdeb/pillow-11.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ebce70c3f486acf7591a3d73431fa504a4e18a9b97ff27f5f47b7368e4b9dd1", size = 2676346 }, - { url = "https://files.pythonhosted.org/packages/80/08/dc268475b22887b816e5dcfae31bce897f524b4646bab130c2142c9b2400/pillow-11.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:c27476257b2fdcd7872d54cfd119b3a9ce4610fb85c8e32b70b42e3680a29a1e", size = 2414623 }, - { url = "https://files.pythonhosted.org/packages/33/49/c8c21e4255b4f4a2c0c68ac18125d7f5460b109acc6dfdef1a24f9b960ef/pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156", size = 3181727 }, - { url = "https://files.pythonhosted.org/packages/6d/f1/f7255c0838f8c1ef6d55b625cfb286835c17e8136ce4351c5577d02c443b/pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772", size = 2999833 }, - { url = "https://files.pythonhosted.org/packages/e2/57/9968114457bd131063da98d87790d080366218f64fa2943b65ac6739abb3/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363", size = 3437472 }, - { url = "https://files.pythonhosted.org/packages/b2/1b/e35d8a158e21372ecc48aac9c453518cfe23907bb82f950d6e1c72811eb0/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0", size = 3459976 }, - { url = "https://files.pythonhosted.org/packages/26/da/2c11d03b765efff0ccc473f1c4186dc2770110464f2177efaed9cf6fae01/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01", size = 3527133 }, - { url = "https://files.pythonhosted.org/packages/79/1a/4e85bd7cadf78412c2a3069249a09c32ef3323650fd3005c97cca7aa21df/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193", size = 3571555 }, - { url = "https://files.pythonhosted.org/packages/69/03/239939915216de1e95e0ce2334bf17a7870ae185eb390fab6d706aadbfc0/pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013", size = 2674713 }, { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734 }, { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841 }, { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470 }, @@ -1856,28 +808,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751 }, ] -[[package]] -name = "platformdirs" -version = "4.3.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, -] - [[package]] name = "platformdirs" version = "4.3.7" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } wheels = [ { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, @@ -1892,15 +826,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] -[[package]] -name = "py" -version = "1.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708 }, -] - [[package]] name = "pybtex" version = "0.24.0" @@ -1930,20 +855,20 @@ wheels = [ [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/b3/c832123f2699892c715fcdfebb1a8fdeffa11bb7b2350e46ecdd76b45a20/pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef", size = 103640 } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/cc/227251b1471f129bc35e966bb0fceb005969023926d744139642d847b7ae/pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", size = 41725 }, + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424 }, ] [[package]] name = "pyflakes" -version = "2.3.1" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a8/0f/0dc480da9162749bf629dca76570972dd9cce5bedc60196a3c912875c87d/pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db", size = 68567 } +sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/11/2a745612f1d3cbbd9c69ba14b1b43a35a2f5c3c81cd0124508c52c64307f/pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", size = 68805 }, + { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164 }, ] [[package]] @@ -1957,7 +882,7 @@ wheels = [ [[package]] name = "pylint" -version = "2.17.7" +version = "3.3.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astroid" }, @@ -1965,73 +890,29 @@ dependencies = [ { name = "dill" }, { name = "isort" }, { name = "mccabe" }, - { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "platformdirs", version = "4.3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "platformdirs" }, { name = "tomlkit" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/e9/21f9ce3e4b81eef011be070a29f8a5c193e2488ed8713a898baa4e8b3362/pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad", size = 434994 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/49/cea450a83079445a84f16050e571a7c383d3f474b13c5caedfebd4e35def/pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87", size = 537178 }, -] - -[[package]] -name = "pyparsing" -version = "3.1.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", ] -sdist = { url = "https://files.pythonhosted.org/packages/83/08/13f3bce01b2061f2bbd582c9df82723de943784cf719a35ac886c652043a/pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032", size = 900231 } +sdist = { url = "https://files.pythonhosted.org/packages/69/a7/113d02340afb9dcbb0c8b25454e9538cd08f0ebf3e510df4ed916caa1a89/pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a", size = 1519586 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/0c/0e3c05b1c87bb6a1c76d281b0f35e78d2d80ac91b5f8f524cebf77f51049/pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", size = 104100 }, + { url = "https://files.pythonhosted.org/packages/31/21/9537fc94aee9ec7316a230a49895266cf02d78aa29b0a2efbc39566e0935/pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", size = 522462 }, ] [[package]] name = "pyparsing" version = "3.2.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } wheels = [ { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, ] -[[package]] -name = "pyproject-api" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "packaging", marker = "python_full_version < '3.9'" }, - { name = "tomli", marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/19/441e0624a8afedd15bbcce96df1b80479dd0ff0d965f5ce8fde4f2f6ffad/pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496", size = 22340 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/f4/3c4ddfcc0c19c217c6de513842d286de8021af2f2ab79bbb86c00342d778/pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228", size = 13100 }, -] - [[package]] name = "pyproject-api" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] dependencies = [ - { name = "packaging", marker = "python_full_version >= '3.9'" }, - { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "packaging" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714 } wheels = [ @@ -2040,35 +921,30 @@ wheels = [ [[package]] name = "pytest" -version = "6.2.5" +version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "atomicwrites", marker = "sys_platform == 'win32'" }, - { name = "attrs" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, - { name = "py" }, - { name = "toml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/24/7d1f2d2537de114bdf1e6875115113ca80091520948d370c964b88070af2/pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", size = 1118720 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/76/86f886e750b81a4357b6ed606b2bcf0ce6d6c27ad3c09ebf63ed674fc86e/pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134", size = 280654 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] [[package]] name = "pytest-cov" -version = "2.12.1" +version = "6.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, { name = "pytest" }, - { name = "toml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/3a/747e953051fd6eb5fb297907a825aad43d94c556d3b9938fc21f3172879f/pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7", size = 60395 } +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/84/576b071aef9ac9301e5c0ff35d117e12db50b87da6f12e745e9c5f745cc2/pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", size = 20441 }, + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 }, ] [[package]] @@ -2083,65 +959,12 @@ 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 }, ] -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, -] - -[[package]] -name = "pywavelets" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6e/d4/008dceeb95fafcf141f39393bdfc10921d0b62a325c2794ac533195a1eb3/PyWavelets-1.4.1.tar.gz", hash = "sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93", size = 4589677 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/92/a78bf0c3d84afd9b17727cce122c3fdb3860a27bd67b32448c7e64301e7b/PyWavelets-1.4.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c", size = 4365967 }, - { url = "https://files.pythonhosted.org/packages/f3/66/2bbcad043383d7be3bca2155972adba1d06be3bc5536afbfa22f1cd99688/PyWavelets-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4", size = 4269548 }, - { url = "https://files.pythonhosted.org/packages/07/fe/90ab3b98dfeb2177e1b8c8ccdd4e777e35dfe0aa98723308bd8f1a97fd47/PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c", size = 6748302 }, - { url = "https://files.pythonhosted.org/packages/3e/fc/651024e8b6e69bef6def2cbe27d520309f4ffc56b8d4885ab7046e1edc6c/PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202", size = 6836957 }, - { url = "https://files.pythonhosted.org/packages/51/af/53bcfea50c24cedb202b0c072193af94a1a611b26ab360082791e455b43f/PyWavelets-1.4.1-cp310-cp310-win32.whl", hash = "sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd", size = 4103794 }, - { url = "https://files.pythonhosted.org/packages/35/12/f1a4f72b5d71497e4200e71e253cc747077d8570b55693faaa7b81fb6dff/PyWavelets-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b", size = 4162789 }, - { url = "https://files.pythonhosted.org/packages/13/e4/86bb218c7926e1da7a52e0696cab120a17c995933f08d8228d9aa83b44c5/PyWavelets-1.4.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875", size = 4349932 }, - { url = "https://files.pythonhosted.org/packages/94/73/4df43d2e18e68c7ea88177c1fa14a25b5813a51b4953dc94c21f2de039d5/PyWavelets-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de", size = 4256446 }, - { url = "https://files.pythonhosted.org/packages/1d/5e/97ff80a20fb22f723f0c3f6f5f407b12579a560abf7c3a8087d052993dd9/PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e", size = 6964351 }, - { url = "https://files.pythonhosted.org/packages/de/a1/cd8a30e061f858f219364554b19d4318276c677a51d956c55fb0b134e8b2/PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784", size = 7040415 }, - { url = "https://files.pythonhosted.org/packages/1d/a1/0f9356779440aaaa35ff82479c40a094419f19ab94a3d5f49e090398959b/PyWavelets-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1", size = 4101666 }, - { url = "https://files.pythonhosted.org/packages/e4/13/9a1632347677e1be27900d9dc922f19bc01440eb8b0c663cea63b35275fc/PyWavelets-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc", size = 4160676 }, - { url = "https://files.pythonhosted.org/packages/2f/52/080267790e23a5186185f2c26d7b774cee754387d1bcb116c7a45f3546f6/PyWavelets-1.4.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966", size = 4349347 }, - { url = "https://files.pythonhosted.org/packages/73/8c/6d50b8e2ee4d12373a63791ad742df1e30ddd5f0f8d1c000c5b6b3afb2c9/PyWavelets-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa", size = 4254092 }, - { url = "https://files.pythonhosted.org/packages/cd/c1/132756d0033b37f4013299ac048bf34d5094673712984edb9e90e8d8a179/PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc", size = 6854556 }, - { url = "https://files.pythonhosted.org/packages/88/4b/b2b2a6f51e47c091c221bfde976a01a7e5f20e7e5e6341b2b9c4db73d2ed/PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4", size = 6942852 }, - { url = "https://files.pythonhosted.org/packages/6c/92/7e900e574575358a5af6ad9f8378d889b1a21e2ba835bae9d0eb7efd505b/PyWavelets-1.4.1-cp38-cp38-win32.whl", hash = "sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd", size = 4111675 }, - { url = "https://files.pythonhosted.org/packages/a9/8f/f80ff31e73385b886c35fb9fb1377849f9c43a3c1195ed8dc8ed8dc1bd88/PyWavelets-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2", size = 4172202 }, - { url = "https://files.pythonhosted.org/packages/9f/67/33b37d53da9d225301e30894db5083569aa670b446253b3906fc0e96119e/PyWavelets-1.4.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6", size = 4366511 }, - { url = "https://files.pythonhosted.org/packages/a0/32/eeeaa4de640a84e2cc35c25aea289367059abce0cac84a9987b139a2a25f/PyWavelets-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426", size = 4268409 }, - { url = "https://files.pythonhosted.org/packages/34/c0/a121306b618af45ff7d769e1bd45ed3d6c798dc7f0094e0b56735388d96e/PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b", size = 6824100 }, - { url = "https://files.pythonhosted.org/packages/5a/98/4549479a32972bdfdd5e75e168219e97f4dfaee535a8308efef7291e8398/PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356", size = 6908506 }, - { url = "https://files.pythonhosted.org/packages/0d/72/db0ef5ca311627f86de89a7af6055301c67490f4160e725cdbd32eea7700/PyWavelets-1.4.1-cp39-cp39-win32.whl", hash = "sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c", size = 4111933 }, - { url = "https://files.pythonhosted.org/packages/02/15/89951f559601fb6755f2231558c33c1b9cbba9e8526906cbc258e27eb53d/PyWavelets-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4", size = 4171580 }, -] - [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, @@ -2169,22 +992,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, - { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218 }, - { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067 }, - { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812 }, - { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531 }, - { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820 }, - { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514 }, - { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702 }, - { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, - { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, - { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, - { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, - { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, - { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, - { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, - { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, - { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, ] [[package]] @@ -2195,8 +1002,7 @@ dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, - { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "urllib3", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } wheels = [ @@ -2204,114 +1010,30 @@ wheels = [ ] [[package]] -name = "scikit-image" -version = "0.21.0" +name = "roman-numerals-py" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "imageio", version = "2.35.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "lazy-loader", marker = "python_full_version < '3.9'" }, - { name = "networkx", version = "3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "packaging", marker = "python_full_version < '3.9'" }, - { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pywavelets", marker = "python_full_version < '3.9'" }, - { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "tifffile", version = "2023.7.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1d/c2/a54d5e6e2d6708e0722a1aaccef4b7cc1e6df6f76c8b4ce98cd6d0c332c3/scikit_image-0.21.0.tar.gz", hash = "sha256:b33e823c54e6f11873ea390ee49ef832b82b9f70752c8759efd09d5a4e3d87f0", size = 22720419 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/a1/6bc36ba38fe9312271cce46cf2025fcc63be096131747a8f41522a57aaef/scikit_image-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:978ac3302252155a8556cdfe067bad2d18d5ccef4e91c2f727bc564ed75566bc", size = 12987373 }, - { url = "https://files.pythonhosted.org/packages/e0/f7/0ec3a2fbed785259176eb2eee7b254fc68c653028907602231cc8ba09da0/scikit_image-0.21.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:82c22e008527e5ee26ab08e3ce919998ef164d538ff30b9e5764b223cfda06b1", size = 12419386 }, - { url = "https://files.pythonhosted.org/packages/ee/5b/3fe767d6ef7cbcf4894355e5905665f99237c5de465a8ca959a05d2320bc/scikit_image-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd29d2631d3e975c377066acfc1f4cb2cc95e2257cf70e7fedfcb96441096e88", size = 13207325 }, - { url = "https://files.pythonhosted.org/packages/70/a9/a9f63dde69ac5a4451d8a0ebdde95824ec31aafcae1c77658a9058e27bb7/scikit_image-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6c12925ceb9f3aede555921e26642d601b2d37d1617002a2636f2cb5178ae2f", size = 13760602 }, - { url = "https://files.pythonhosted.org/packages/f3/93/65601f7577d6fd49ec23bf8fb58c04d8170b06a1544452ae2ea9f59bf11f/scikit_image-0.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f538d4de77e4f3225d068d9ea2965bed3f7dda7f457a8f89634fa22ffb9ad8c", size = 22777245 }, - { url = "https://files.pythonhosted.org/packages/08/53/f28cfb52248665b42db7e45a36ffc3a304fef46b308e5065fe2046e78daf/scikit_image-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec9bab6920ac43037d7434058b67b5778d42c60f67b8679239f48a471e7ed6f8", size = 12911110 }, - { url = "https://files.pythonhosted.org/packages/20/54/06f821fd78c24f7047629dc4c8ed948101fc91fdf660ee3263d870220ae8/scikit_image-0.21.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:a54720430dba833ffbb6dedd93d9f0938c5dd47d20ab9ba3e4e61c19d95f6f19", size = 12333056 }, - { url = "https://files.pythonhosted.org/packages/31/cf/9e8e819a8d90fb74ec183a0c3e8e182587929845c93779f99439cd270f10/scikit_image-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e40dd102da14cdadc09210f930b4556c90ff8f99cd9d8bcccf9f73a86c44245", size = 13204303 }, - { url = "https://files.pythonhosted.org/packages/22/c3/c5f3c351d6337a18d07c3fb04475626c106cd3dc3d59b85ec50d07656db0/scikit_image-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff5719c7eb99596a39c3e1d9b564025bae78ecf1da3ee6842d34f6965b5f1474", size = 13726119 }, - { url = "https://files.pythonhosted.org/packages/08/c0/8085c5fd2f7f7514a0c5031b666171d5828ac5b3c9cf5d0ecd19688d5407/scikit_image-0.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:146c3824253eee9ff346c4ad10cb58376f91aefaf4a4bb2fe11aa21691f7de76", size = 22753693 }, - { url = "https://files.pythonhosted.org/packages/35/e4/d5d1574d09f30a4df757edf4213ce8e764aebe0f1642475cf384f9fa33bb/scikit_image-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e1b09f81a99c9c390215929194847b3cd358550b4b65bb6e42c5393d69cb74a", size = 12886049 }, - { url = "https://files.pythonhosted.org/packages/62/9b/8fd51371f3fd4ce06092d1f4740ec5a874a996727117e076c03755e8c777/scikit_image-0.21.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:9f7b5fb4a22f0d5ae0fa13beeb887c925280590145cd6d8b2630794d120ff7c7", size = 12315195 }, - { url = "https://files.pythonhosted.org/packages/fa/2b/ffecc6f29b48d1d46dc3bb7b4c908490260c3a0d69ac2d248d846b90d505/scikit_image-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4814033717f0b6491fee252facb9df92058d6a72ab78dd6408a50f3915a88b8", size = 13304683 }, - { url = "https://files.pythonhosted.org/packages/33/29/1d696450464d6e13358d3ef185a1fb14a11181c5dab1eb2837c02be86373/scikit_image-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0d6ed6502cca0c9719c444caafa0b8cda0f9e29e01ca42f621a240073284be", size = 13870072 }, - { url = "https://files.pythonhosted.org/packages/d7/d1/a4c715ad640c9eb0daaa77c4ce561b06e086bec44cbc79083e3548b00b76/scikit_image-0.21.0-cp38-cp38-win_amd64.whl", hash = "sha256:9194cb7bc21215fde6c1b1e9685d312d2aa8f65ea9736bc6311126a91c860032", size = 22712424 }, - { url = "https://files.pythonhosted.org/packages/ac/96/6a64d241498380dc5f0dca8e48981cd610d31d661a59f90d5ac242546906/scikit_image-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54df1ddc854f37a912d42bc724e456e86858107e94048a81a27720bc588f9937", size = 13011056 }, - { url = "https://files.pythonhosted.org/packages/c4/09/0b465a48f9bc7e848538f82e62811978932132b1edd50da758a9243cef5a/scikit_image-0.21.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:c01e3ab0a1fabfd8ce30686d4401b7ed36e6126c9d4d05cb94abf6bdc46f7ac9", size = 12424218 }, - { url = "https://files.pythonhosted.org/packages/2f/35/fb5f6a7d46c5dfcbb44d55cff39eb159e74752389097deee1af18a1447ce/scikit_image-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ef5d8d1099317b7b315b530348cbfa68ab8ce32459de3c074d204166951025c", size = 13313982 }, - { url = "https://files.pythonhosted.org/packages/19/bd/a53569a0a698d925eb46dbea0bd3b6b62e7287a9ec88b5a03efa8ebd5b14/scikit_image-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b1e96c59cab640ca5c5b22c501524cfaf34cbe0cb51ba73bd9a9ede3fb6e1d", size = 13828794 }, - { url = "https://files.pythonhosted.org/packages/32/b2/1811645651153407f1e715b75afe9962d87582bee70b42c8671c255f8fe6/scikit_image-0.21.0-cp39-cp39-win_amd64.whl", hash = "sha256:9cffcddd2a5594c0a06de2ae3e1e25d662745a26f94fda31520593669677c010", size = 22896233 }, -] - -[[package]] -name = "scikit-image" -version = "0.24.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "imageio", version = "2.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "lazy-loader", marker = "python_full_version == '3.9.*'" }, - { name = "networkx", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "packaging", marker = "python_full_version == '3.9.*'" }, - { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "tifffile", version = "2024.8.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/c5/bcd66bf5aae5587d3b4b69c74bee30889c46c9778e858942ce93a030e1f3/scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab", size = 22693928 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/99/89/3fcd68d034db5d29c974e964d03deec9d0fbf9410ff0a0b95efff70947f6/scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7", size = 12864601 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/50/b2/d5e97115733e2dc657e99868ae0237705b79d0c81f6ced21b8f0799a30d1/scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c", size = 12843506 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/d9/17/b561823143eb931de0f82fed03ae128ef954a9641309602ea0901c357f95/scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc", size = 12949363 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/9d/63/233300aa76c65a442a301f9d2416a9b06c91631287bd6dd3d6b620040096/scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69", size = 12891952 }, +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, ] [[package]] name = "scikit-image" version = "0.25.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] dependencies = [ - { name = "imageio", version = "2.37.0", source = { registry = "https://pypi.org/simple" }, 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 = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "scipy", version = "1.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "tifffile", version = "2025.3.30", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594 } 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908 }, { 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 }, { 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 }, { 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 }, @@ -2332,133 +1054,46 @@ wheels = [ [[package]] name = "scikit-learn" -version = "0.24.2" +version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, - { name = "scipy", version = "1.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "threadpoolctl", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "threadpoolctl", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/04/507280f20fafc8bc94b41e0592938c6f4a910d0e066be7c8ff1299628f5d/scikit-learn-0.24.2.tar.gz", hash = "sha256:d14701a12417930392cd3898e9646cf5670c190b933625ebe7511b1f7d7b8736", size = 7524030 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/6c/31c623a656ab66e938b2432cb8e41dc5497dec670ff07e1e74989ee0ab77/scikit_learn-0.24.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:90a297330f608adeb4d2e9786c6fda395d3150739deb3d42a86d9a4c2d15bc1d", size = 7198770 }, - { url = "https://files.pythonhosted.org/packages/89/ac/5760aed927165f5b1e0b7543e804f6b29b768f8992e5255fdb3495b09bde/scikit_learn-0.24.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f1d2108e770907540b5248977e4cff9ffaf0f73d0d13445ee938df06ca7579c6", size = 19350355 }, - { url = "https://files.pythonhosted.org/packages/23/87/5eb251917dc7d42119565a3a8a1937f8186399c9df8fbb1537ced97c0499/scikit_learn-0.24.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1eec963fe9ffc827442c2e9333227c4d49749a44e592f305398c1db5c1563393", size = 20429843 }, - { url = "https://files.pythonhosted.org/packages/8a/e2/7d0a3f450d3a99c1a2f3485084e437b1d0ad69d5ded89def17e644bd8e10/scikit_learn-0.24.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:2db429090b98045d71218a9ba913cc9b3fe78e0ba0b6b647d8748bc6d5a44080", size = 23704391 }, - { url = "https://files.pythonhosted.org/packages/36/dd/06b6f0ad7b86c3edc3a02d255f3fcc98e444a847cea10b3a0182251ca7b6/scikit_learn-0.24.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:62214d2954377fcf3f31ec867dd4e436df80121e7a32947a0b3244f58f45e455", size = 24885741 }, - { url = "https://files.pythonhosted.org/packages/35/ed/4bac8cc77baa335f31e1ea7dd91fc3b0793df755192aa384c7ca4dee4841/scikit_learn-0.24.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8fac72b9688176922f9f54fda1ba5f7ffd28cbeb9aad282760186e8ceba9139a", size = 26530912 }, - { url = "https://files.pythonhosted.org/packages/9a/da/c0fdac5d401c11dfb4f6d881fccd4000add616ade3b0e45e9f35c14ca001/scikit_learn-0.24.2-cp38-cp38-win32.whl", hash = "sha256:ae426e3a52842c6b6d77d00f906b6031c8c2cfdfabd6af7511bb4bc9a68d720e", size = 6142216 }, - { url = "https://files.pythonhosted.org/packages/9a/ff/5951e53ffed8d0fdb958883bd8a49b050f07c9b0c96a643607006bf690fe/scikit_learn-0.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:038f4e9d6ef10e1f3fe82addc3a14735c299866eb10f2c77c090410904828312", size = 6896277 }, - { url = "https://files.pythonhosted.org/packages/c6/6a/41fe5034d4f85c24aea9b7dca1f070bcca2ec29251d0624e87436bf5a2c0/scikit_learn-0.24.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:48f273836e19901ba2beecd919f7b352f09310ce67c762f6e53bc6b81cacf1f0", size = 7299152 }, - { url = "https://files.pythonhosted.org/packages/02/7a/1ec25170b09942fd099110ba213ec9e09b1e9e0a5aeeb5596e42a7e9dd9d/scikit_learn-0.24.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a2a47449093dcf70babc930beba2ca0423cb7df2fa5fd76be5260703d67fa574", size = 18565258 }, - { url = "https://files.pythonhosted.org/packages/13/ac/7ea73841bbed1f1c60f6e960dc9bbf1401f84926ef9db69cfce07d61127a/scikit_learn-0.24.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0e71ce9c7cbc20f6f8b860107ce15114da26e8675238b4b82b7e7cd37ca0c087", size = 19556544 }, - { url = "https://files.pythonhosted.org/packages/82/42/397ba53eab6062b8a4e2a0a7681aed66e3e7912e59628aae592376209ffd/scikit_learn-0.24.2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2754c85b2287333f9719db7f23fb7e357f436deed512db3417a02bf6f2830aa5", size = 22675859 }, - { url = "https://files.pythonhosted.org/packages/04/e2/b43d4205124dd4c1f14606b2e2d78303db993c6653a90bf11dd0ffe23b5b/scikit_learn-0.24.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7be1b88c23cfac46e06404582215a917017cd2edaa2e4d40abe6aaff5458f24b", size = 23809736 }, - { url = "https://files.pythonhosted.org/packages/bf/48/76f46db8936adf6a279daa82341f53b89cac01f40c683a3f22bc9a4362a5/scikit_learn-0.24.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:4e6198675a6f9d333774671bd536668680eea78e2e81c0b19e57224f58d17f37", size = 25544637 }, - { url = "https://files.pythonhosted.org/packages/7c/2d/80fbde8fcb68b9961afbd8c810d8451577c74cdd46a909c9e39481531127/scikit_learn-0.24.2-cp39-cp39-win32.whl", hash = "sha256:cbdb0b3db99dd1d5f69d31b4234367d55475add31df4d84a3bd690ef017b55e2", size = 6129308 }, - { url = "https://files.pythonhosted.org/packages/eb/bb/0bf5164cf4f31eb01feff6b1c3ae205ebda58aa3fc6beaaa53617185b0e6/scikit_learn-0.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:40556bea1ef26ef54bc678d00cf138a63069144a0b5f3a436eecd8f3468b903e", size = 6881380 }, -] - -[[package]] -name = "scipy" -version = "1.10.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/84/a9/2bf119f3f9cff1f376f924e39cfae18dec92a1514784046d185731301281/scipy-1.10.1.tar.gz", hash = "sha256:2cf9dfb80a7b4589ba4c40ce7588986d6d5cebc5457cad2c2880f6bc2d42f3a5", size = 42407997 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/ac/b1f1bbf7b01d96495f35be003b881f10f85bf6559efb6e9578da832c2140/scipy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7354fd7527a4b0377ce55f286805b34e8c54b91be865bac273f527e1b839019", size = 35093243 }, - { url = "https://files.pythonhosted.org/packages/ea/e5/452086ebed676ce4000ceb5eeeb0ee4f8c6f67c7e70fb9323a370ff95c1f/scipy-1.10.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4b3f429188c66603a1a5c549fb414e4d3bdc2a24792e061ffbd607d3d75fd84e", size = 28772969 }, - { url = "https://files.pythonhosted.org/packages/04/0b/a1b119c869b79a2ab459b7f9fd7e2dea75a9c7d432e64e915e75586bd00b/scipy-1.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1553b5dcddd64ba9a0d95355e63fe6c3fc303a8fd77c7bc91e77d61363f7433f", size = 30886961 }, - { url = "https://files.pythonhosted.org/packages/1f/4b/3bacad9a166350cb2e518cea80ab891016933cc1653f15c90279512c5fa9/scipy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c0ff64b06b10e35215abce517252b375e580a6125fd5fdf6421b98efbefb2d2", size = 34422544 }, - { url = "https://files.pythonhosted.org/packages/ec/e3/b06ac3738bf365e89710205a471abe7dceec672a51c244b469bc5d1291c7/scipy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:fae8a7b898c42dffe3f7361c40d5952b6bf32d10c4569098d276b4c547905ee1", size = 42484848 }, - { url = "https://files.pythonhosted.org/packages/e7/53/053cd3669be0d474deae8fe5f757bff4c4f480b8a410231e0631c068873d/scipy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f1564ea217e82c1bbe75ddf7285ba0709ecd503f048cb1236ae9995f64217bd", size = 35003170 }, - { url = "https://files.pythonhosted.org/packages/0d/3e/d05b9de83677195886fb79844fcca19609a538db63b1790fa373155bc3cf/scipy-1.10.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d925fa1c81b772882aa55bcc10bf88324dadb66ff85d548c71515f6689c6dac5", size = 28717513 }, - { url = "https://files.pythonhosted.org/packages/a5/3d/b69746c50e44893da57a68457da3d7e5bb75f6a37fbace3769b70d017488/scipy-1.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaea0a6be54462ec027de54fca511540980d1e9eea68b2d5c1dbfe084797be35", size = 30687257 }, - { url = "https://files.pythonhosted.org/packages/21/cd/fe2d4af234b80dc08c911ce63fdaee5badcdde3e9bcd9a68884580652ef0/scipy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15a35c4242ec5f292c3dd364a7c71a61be87a3d4ddcc693372813c0b73c9af1d", size = 34124096 }, - { url = "https://files.pythonhosted.org/packages/65/76/903324159e4a3566e518c558aeb21571d642f781d842d8dd0fd9c6b0645a/scipy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b8e0bcb877faf0abfb613d51026cd5cc78918e9530e375727bf0625c82788f", size = 42238704 }, - { url = "https://files.pythonhosted.org/packages/a0/e3/37508a11dae501349d7c16e4dd18c706a023629eedc650ee094593887a89/scipy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5678f88c68ea866ed9ebe3a989091088553ba12c6090244fdae3e467b1139c35", size = 35041063 }, - { url = "https://files.pythonhosted.org/packages/93/4a/50c436de1353cce8b66b26e49a687f10b91fe7465bf34e4565d810153003/scipy-1.10.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:39becb03541f9e58243f4197584286e339029e8908c46f7221abeea4b749fa88", size = 28797694 }, - { url = "https://files.pythonhosted.org/packages/d2/b5/ff61b79ad0ebd15d87ade10e0f4e80114dd89fac34a5efade39e99048c91/scipy-1.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bce5869c8d68cf383ce240e44c1d9ae7c06078a9396df68ce88a1230f93a30c1", size = 31024657 }, - { url = "https://files.pythonhosted.org/packages/69/f0/fb07a9548e48b687b8bf2fa81d71aba9cfc548d365046ca1c791e24db99d/scipy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07c3457ce0b3ad5124f98a86533106b643dd811dd61b548e78cf4c8786652f6f", size = 34540352 }, - { url = "https://files.pythonhosted.org/packages/32/8e/7f403535ddf826348c9b8417791e28712019962f7e90ff845896d6325d09/scipy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:049a8bbf0ad95277ffba9b3b7d23e5369cc39e66406d60422c8cfef40ccc8415", size = 42215036 }, - { url = "https://files.pythonhosted.org/packages/d9/7d/78b8035bc93c869b9f17261c87aae97a9cdb937f65f0d453c2831aa172fc/scipy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd9f1027ff30d90618914a64ca9b1a77a431159df0e2a195d8a9e8a04c78abf9", size = 35158611 }, - { url = "https://files.pythonhosted.org/packages/e7/f0/55d81813b1a4cb79ce7dc8290eac083bf38bfb36e1ada94ea13b7b1a5f79/scipy-1.10.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:79c8e5a6c6ffaf3a2262ef1be1e108a035cf4f05c14df56057b64acc5bebffb6", size = 28902591 }, - { url = "https://files.pythonhosted.org/packages/77/d1/722c457b319eed1d642e0a14c9be37eb475f0e6ed1f3401fa480d5d6d36e/scipy-1.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51af417a000d2dbe1ec6c372dfe688e041a7084da4fdd350aeb139bd3fb55353", size = 30960654 }, - { url = "https://files.pythonhosted.org/packages/5d/30/b2a2a5bf1a3beefb7609fb871dcc6aef7217c69cef19a4631b7ab5622a8a/scipy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b4735d6c28aad3cdcf52117e0e91d6b39acd4272f3f5cd9907c24ee931ad601", size = 34458863 }, - { url = "https://files.pythonhosted.org/packages/35/20/0ec6246bbb43d18650c9a7cad6602e1a84fd8f9564a9b84cc5faf1e037d0/scipy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ff7f37b1bf4417baca958d254e8e2875d0cc23aaadbe65b3d5b3077b0eb23ea", size = 42509516 }, -] - -[[package]] -name = "scipy" -version = "1.13.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440 }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620 }, + { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234 }, + { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155 }, + { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069 }, + { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809 }, + { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516 }, + { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837 }, + { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728 }, + { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700 }, + { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613 }, + { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001 }, + { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360 }, + { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004 }, + { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776 }, + { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865 }, + { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804 }, + { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530 }, + { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852 }, + { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256 }, ] [[package]] name = "scipy" version = "1.15.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/df/ef233fff6838fe6f7840d69b5ef9f20d2b5c912a8727b21ebf876cb15d54/scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9", size = 38692502 }, - { url = "https://files.pythonhosted.org/packages/5c/20/acdd4efb8a68b842968f7bc5611b1aeb819794508771ad104de418701422/scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5", size = 30085508 }, - { url = "https://files.pythonhosted.org/packages/42/55/39cf96ca7126f1e78ee72a6344ebdc6702fc47d037319ad93221063e6cf4/scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e", size = 22359166 }, - { url = "https://files.pythonhosted.org/packages/51/48/708d26a4ab8a1441536bf2dfcad1df0ca14a69f010fba3ccbdfc02df7185/scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9", size = 25112047 }, - { url = "https://files.pythonhosted.org/packages/dd/65/f9c5755b995ad892020381b8ae11f16d18616208e388621dfacc11df6de6/scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3", size = 35536214 }, - { url = "https://files.pythonhosted.org/packages/de/3c/c96d904b9892beec978562f64d8cc43f9cca0842e65bd3cd1b7f7389b0ba/scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d", size = 37646981 }, - { url = "https://files.pythonhosted.org/packages/3d/74/c2d8a24d18acdeae69ed02e132b9bc1bb67b7bee90feee1afe05a68f9d67/scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58", size = 37230048 }, - { url = "https://files.pythonhosted.org/packages/42/19/0aa4ce80eca82d487987eff0bc754f014dec10d20de2f66754fa4ea70204/scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa", size = 40010322 }, - { url = "https://files.pythonhosted.org/packages/d0/d2/f0683b7e992be44d1475cc144d1f1eeae63c73a14f862974b4db64af635e/scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65", size = 41233385 }, { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 }, { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 }, { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 }, @@ -2499,11 +1134,11 @@ wheels = [ [[package]] name = "setuptools" -version = "58.5.3" +version = "78.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/00/05f51ceab8d3b9be4295000d8be4c830c53e5477755888994e9825606cd9/setuptools-58.5.3.tar.gz", hash = "sha256:dae6b934a965c8a59d6d230d3867ec408bb95e73bd538ff77e71fedf1eaca729", size = 2269854 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/e9/84e2865fddfaba4506bc5d293d2a535bf27e31b12ca16d31564f8ce28cdb/setuptools-58.5.3-py3-none-any.whl", hash = "sha256:a481fbc56b33f5d8f6b33dce41482e64c68b668be44ff42922903b03872590bf", size = 946828 }, + { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 }, ] [[package]] @@ -2526,37 +1161,30 @@ wheels = [ [[package]] name = "sphinx" -version = "4.5.0" +version = "8.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "alabaster", version = "0.7.13", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "alabaster" }, { name = "babel" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "docutils" }, { name = "imagesize" }, - { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "importlib-metadata", version = "8.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "jinja2" }, { name = "packaging" }, { name = "pygments" }, { name = "requests" }, + { name = "roman-numerals-py" }, { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp", version = "1.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "sphinxcontrib-applehelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "sphinxcontrib-devhelp", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "sphinxcontrib-devhelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "sphinxcontrib-htmlhelp", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "sphinxcontrib-htmlhelp", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "sphinxcontrib-qthelp", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "sphinxcontrib-serializinghtml", version = "1.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "sphinxcontrib-serializinghtml", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/b9/b831ea20dde3c3b726e41403eaee92cc448083cef310790c31c6ccfb22e3/Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6", size = 6698212 } +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/96/9cbbc7103fb482d5809fe4976ecb9b627058210d02817fcbfeebeaa8f762/Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226", size = 3099508 }, + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741 }, ] [[package]] @@ -2573,40 +1201,22 @@ wheels = [ [[package]] name = "sphinx-rtd-theme" -version = "1.3.0" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "sphinx" }, { name = "sphinxcontrib-jquery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/3e/477c5b3ed78b6818d673f63512db12ace8c89e83eb9eecc913f9e2cc8416/sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931", size = 2785069 } +sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/01/76f40a18e9209bb098c1c1313c823dbbd001b23a2db71e7fd4eb5a48559c/sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0", size = 2824803 }, -] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/32/df/45e827f4d7e7fcc84e853bcef1d836effd762d63ccb86f43ede4e98b478c/sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e", size = 24766 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/c1/5e2cafbd03105ce50d8500f9b4e8a6e8d02e22d0475b574c3b3e9451a15f/sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228", size = 120601 }, + { 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 }, ] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, @@ -2618,8 +1228,6 @@ version = "2.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, - { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "importlib-metadata", version = "8.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, { name = "pybtex" }, { name = "pybtex-docutils" }, { name = "setuptools", marker = "python_full_version >= '3.12'" }, @@ -2645,55 +1253,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/8d/7a7dd95ad1eedec8dc770570c8b1f3dc1d13357383635607b6629ccf329c/sphinxcontrib.datatemplates-0.11.0-py3-none-any.whl", hash = "sha256:88d02f5edab32b88211ebb72a90553e3676a5737877bad1de412f84058ac282e", size = 12493 }, ] -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/98/33/dc28393f16385f722c893cb55539c641c9aaec8d1bc1c15b69ce0ac2dbb3/sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4", size = 17398 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", size = 84690 }, -] - [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } wheels = [ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, ] -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/47/64cff68ea3aa450c373301e5bebfbb9fce0a3e70aca245fcadd4af06cd75/sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff", size = 27967 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/ee/a1f5e39046cbb5f8bc8fba87d1ddf1c6643fbc9194e58d26e606de4b9074/sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903", size = 99833 }, -] - [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, @@ -2720,28 +1292,10 @@ 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 }, ] -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/8e/c4846e59f38a5f2b4a0e3b27af38f2fcf904d4bfd82095bf92de0b114ebd/sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", size = 21658 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/14/05f9206cf4e9cfca1afb5fd224c7cd434dcc3a433d6d9e4e0264d29c6cdb/sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6", size = 90609 }, -] - [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } wheels = [ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, @@ -2759,155 +1313,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/d9/67a79080b5d9fcb367470af9e525a9c53122e95744665de09462dcd676d8/sphinxcontrib_runcmd-0.2.0-py2.py3-none-any.whl", hash = "sha256:7b739b68e27210b4c7c12ba16e5b3da7b313c49991401f896d29bea0f0771934", size = 6043 }, ] -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/b5/72/835d6fadb9e5d02304cf39b18f93d227cd93abd3c41ebf58e6853eeb1455/sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952", size = 21019 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/77/5464ec50dd0f1c1037e3c93249b040c8fc8078fdda97530eeb02424b6eea/sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", size = 94021 }, -] - [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] -[[package]] -name = "threadpoolctl" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 }, -] - [[package]] name = "threadpoolctl" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } wheels = [ { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, ] -[[package]] -name = "tifffile" -version = "2023.7.10" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/22/4d19feaba862f06f6392499d5617f96b0d8eb9a876e33e9b6aab292b88f2/tifffile-2023.7.10.tar.gz", hash = "sha256:c06ec460926d16796eeee249a560bcdddf243daae36ac62af3c84a953cd60b4a", size = 345689 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/a3/68d17088a4f09565bc7341fd20490da8191ec4cddde479daaabbe07bb603/tifffile-2023.7.10-py3-none-any.whl", hash = "sha256:94dfdec321ace96abbfe872a66cfd824800c099a2db558443453eebc2c11b304", size = 220889 }, -] - -[[package]] -name = "tifffile" -version = "2024.8.30" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/30/7017e5560154c100cad3a801c02adb48879cd8e8cb862b82696d84187184/tifffile-2024.8.30.tar.gz", hash = "sha256:2c9508fe768962e30f87def61819183fb07692c258cb175b3c114828368485a4", size = 365714 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/4f/73714b1c1d339b1545cac28764e39f88c69468b5e10e51f327f9aa9d55b9/tifffile-2024.8.30-py3-none-any.whl", hash = "sha256:8bc59a8f02a2665cd50a910ec64961c5373bee0b8850ec89d3b7b485bf7be7ad", size = 227262 }, -] - [[package]] name = "tifffile" version = "2025.3.30" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", -] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3c/54/d5ebe66a9de349b833e570e87bdbd9eec76ec54bd505c24b0591a15783ad/tifffile-2025.3.30.tar.gz", hash = "sha256:3cdee47fe06cd75367c16bc3ff34523713156dae6cd498e3a392e5b39a51b789", size = 366039 } wheels = [ { url = "https://files.pythonhosted.org/packages/6e/be/10d23cfd4078fbec6aba768a357eff9e70c0b6d2a07398425985c524ad2a/tifffile-2025.3.30-py3-none-any.whl", hash = "sha256:0ed6eee7b66771db2d1bfc42262a51b01887505d35539daef118f4ff8c0f629c", size = 226837 }, ] -[[package]] -name = "toml" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, -] - -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, -] - [[package]] name = "tomlkit" version = "0.13.2" @@ -2925,16 +1360,11 @@ dependencies = [ { name = "cachetools" }, { name = "chardet" }, { name = "colorama" }, - { name = "filelock", version = "3.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "filelock", version = "3.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "filelock" }, { name = "packaging" }, - { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "platformdirs", version = "4.3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "platformdirs" }, { name = "pluggy" }, - { name = "pyproject-api", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pyproject-api", version = "1.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "pyproject-api" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fe/87/692478f0a194f1cad64803692642bd88c12c5b64eee16bf178e4a32e979c/tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52", size = 196255 } @@ -2942,77 +1372,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/38/33348de6fc4b1afb3d76d8485c8aecbdabcfb3af8da53d40c792332e2b37/tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c", size = 172420 }, ] -[[package]] -name = "tox-uv" -version = "1.13.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "importlib-resources", version = "6.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "packaging", marker = "python_full_version < '3.9'" }, - { name = "tox", marker = "python_full_version < '3.9'" }, - { name = "typing-extensions", marker = "python_full_version < '3.9'" }, - { name = "uv", marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/93/1f06c3cbfd4c1aa23859d49a76c7e65b51e60715bc22b2dd16cbff9c1e71/tox_uv-1.13.1.tar.gz", hash = "sha256:a8504b8db4bf6c81cba7cd3518851a3f1e0f6991d22272a4cc08ebe1b7f38cca", size = 15645 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/8e/94afb25547f5e4987801e8f6aa11e357190f72f31eb363267a3cb2fa6a88/tox_uv-1.13.1-py3-none-any.whl", hash = "sha256:b163dd28ca37a9f4c6d8cbac11153be27c2e929b58bcae62e323ffa8f71c327d", size = 13383 }, -] - [[package]] name = "tox-uv" version = "1.25.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] dependencies = [ - { name = "packaging", marker = "python_full_version >= '3.9'" }, - { name = "tox", marker = "python_full_version >= '3.9'" }, - { name = "typing-extensions", marker = "python_full_version == '3.9.*'" }, - { name = "uv", marker = "python_full_version >= '3.9'" }, + { name = "packaging" }, + { name = "tox" }, + { name = "uv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5d/3a/3e445f25978a716ba6674f33f687d9336d0312086a277a778a5e9e9220d7/tox_uv-1.25.0.tar.gz", hash = "sha256:59ee5e694c41fef7bbcf058f22a5f9b6a8509698def2ea60c08554f4e36b9fcc", size = 21114 } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a7/f5c29e0e6faaccefcab607f672b176927144e9412c8183d21301ea2a6f6c/tox_uv-1.25.0-py3-none-any.whl", hash = "sha256:50cfe7795dcd49b2160d7d65b5ece8717f38cfedc242c852a40ec0a71e159bf7", size = 16431 }, ] -[[package]] -name = "typing-extensions" -version = "4.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, -] - [[package]] name = "urllib3" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", - "python_full_version == '3.10.*'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } wheels = [ { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, @@ -3049,122 +1426,10 @@ version = "20.30.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, - { name = "filelock", version = "3.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "filelock", version = "3.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "platformdirs", version = "4.3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "filelock" }, + { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 } wheels = [ { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 }, ] - -[[package]] -name = "wrapt" -version = "1.17.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307 }, - { url = "https://files.pythonhosted.org/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486 }, - { url = "https://files.pythonhosted.org/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777 }, - { url = "https://files.pythonhosted.org/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314 }, - { url = "https://files.pythonhosted.org/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947 }, - { url = "https://files.pythonhosted.org/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778 }, - { url = "https://files.pythonhosted.org/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716 }, - { url = "https://files.pythonhosted.org/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548 }, - { url = "https://files.pythonhosted.org/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334 }, - { url = "https://files.pythonhosted.org/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427 }, - { url = "https://files.pythonhosted.org/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774 }, - { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308 }, - { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488 }, - { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776 }, - { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776 }, - { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420 }, - { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199 }, - { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307 }, - { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025 }, - { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879 }, - { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419 }, - { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773 }, - { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799 }, - { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821 }, - { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919 }, - { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721 }, - { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899 }, - { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222 }, - { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707 }, - { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685 }, - { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567 }, - { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672 }, - { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865 }, - { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800 }, - { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824 }, - { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920 }, - { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690 }, - { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861 }, - { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174 }, - { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721 }, - { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763 }, - { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585 }, - { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676 }, - { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871 }, - { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312 }, - { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062 }, - { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155 }, - { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471 }, - { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208 }, - { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339 }, - { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232 }, - { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476 }, - { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377 }, - { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986 }, - { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750 }, - { url = "https://files.pythonhosted.org/packages/0c/66/95b9e90e6e1274999b183c9c3f984996d870e933ca9560115bd1cd1d6f77/wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9", size = 53234 }, - { url = "https://files.pythonhosted.org/packages/a4/b6/6eced5e2db5924bf6d9223d2bb96b62e00395aae77058e6a9e11bf16b3bd/wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119", size = 38462 }, - { url = "https://files.pythonhosted.org/packages/5d/a4/c8472fe2568978b5532df84273c53ddf713f689d408a4335717ab89547e0/wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6", size = 38730 }, - { url = "https://files.pythonhosted.org/packages/3c/70/1d259c6b1ad164eb23ff70e3e452dd1950f96e6473f72b7207891d0fd1f0/wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9", size = 86225 }, - { url = "https://files.pythonhosted.org/packages/a9/68/6b83367e1afb8de91cbea4ef8e85b58acdf62f034f05d78c7b82afaa23d8/wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a", size = 78055 }, - { url = "https://files.pythonhosted.org/packages/0d/21/09573d2443916705c57fdab85d508f592c0a58d57becc53e15755d67fba2/wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2", size = 85592 }, - { url = "https://files.pythonhosted.org/packages/45/ce/700e17a852dd5dec894e241c72973ea82363486bcc1fb05d47b4fbd1d683/wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a", size = 83906 }, - { url = "https://files.pythonhosted.org/packages/37/14/bd210faf0a66faeb8529d42b6b45a25d6aa6ce25ddfc19168e4161aed227/wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04", size = 76763 }, - { url = "https://files.pythonhosted.org/packages/34/0c/85af70d291f44659c422416f0272046109e785bf6db8c081cfeeae5715c5/wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f", size = 83573 }, - { url = "https://files.pythonhosted.org/packages/f8/1e/b215068e824878f69ea945804fa26c176f7c2735a3ad5367d78930bd076a/wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7", size = 36408 }, - { url = "https://files.pythonhosted.org/packages/52/27/3dd9ad5f1097b33c95d05929e409cc86d7c765cb5437b86694dc8f8e9af0/wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3", size = 38737 }, - { url = "https://files.pythonhosted.org/packages/8a/f4/6ed2b8f6f1c832933283974839b88ec7c983fd12905e01e97889dadf7559/wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a", size = 53308 }, - { url = "https://files.pythonhosted.org/packages/a2/a9/712a53f8f4f4545768ac532619f6e56d5d0364a87b2212531685e89aeef8/wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061", size = 38489 }, - { url = "https://files.pythonhosted.org/packages/fa/9b/e172c8f28a489a2888df18f953e2f6cb8d33b1a2e78c9dfc52d8bf6a5ead/wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82", size = 38776 }, - { url = "https://files.pythonhosted.org/packages/cf/cb/7a07b51762dcd59bdbe07aa97f87b3169766cadf240f48d1cbe70a1be9db/wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9", size = 83050 }, - { url = "https://files.pythonhosted.org/packages/a5/51/a42757dd41032afd6d8037617aa3bc6803ba971850733b24dfb7d5c627c4/wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f", size = 74718 }, - { url = "https://files.pythonhosted.org/packages/bf/bb/d552bfe47db02fcfc950fc563073a33500f8108efa5f7b41db2f83a59028/wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b", size = 82590 }, - { url = "https://files.pythonhosted.org/packages/77/99/77b06b3c3c410dbae411105bf22496facf03a5496bfaca8fbcf9da381889/wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f", size = 81462 }, - { url = "https://files.pythonhosted.org/packages/2d/21/cf0bd85ae66f92600829ea1de8e1da778e5e9f6e574ccbe74b66db0d95db/wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8", size = 74309 }, - { url = "https://files.pythonhosted.org/packages/6d/16/112d25e9092398a0dd6fec50ab7ac1b775a0c19b428f049785096067ada9/wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9", size = 81081 }, - { url = "https://files.pythonhosted.org/packages/2b/49/364a615a0cc0872685646c495c7172e4fc7bf1959e3b12a1807a03014e05/wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb", size = 36423 }, - { url = "https://files.pythonhosted.org/packages/00/ad/5d2c1b34ba3202cd833d9221833e74d6500ce66730974993a8dc9a94fb8c/wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb", size = 38772 }, - { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, -] - -[[package]] -name = "zipp" -version = "3.20.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200 }, -] - -[[package]] -name = "zipp" -version = "3.21.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.9.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, -] diff --git a/tests/tox.ini b/tests/tox.ini index e62207d..4cc683c 100644 --- a/tests/tox.ini +++ b/tests/tox.ini @@ -3,10 +3,10 @@ [tox] # A list of environments that will be run by default when running tox without specifying any environment -envlist = py38,py39,coverage,pylint,flake8,docs +envlist = py311,py312,py313,coverage,pylint,flake8,docs # Base configuration for test environments that tox will fallback to for missing values; this avoids having to repeat the same configuration for the -# unit test environments py38 and py39 over and over again +# unit test environments py311, py312, and py313 over and over again [testenv] setenv = COVERAGE_FILE = .coverage.{envname} @@ -21,14 +21,14 @@ commands = [testenv:coverage] setenv = COVERAGE_FILE = /.coverage -depends = py38,py39 +depends = py311,py312,py313 commands = uv run coverage combine uv run coverage report -m # A test environment that will build the documentation using Sphinx [testenv:docs] -basepython = python3.9.21 +basepython = python3.13.3 commands = uv run sphinx-build \ --color \ @@ -42,7 +42,7 @@ commands = # A test environment that will run the Flake8 meta-linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples [testenv:flake8] -basepython = python3.9.21 +basepython = python3.13.3 commands = uv run flake8 \ --config "linters/.flake8" \ @@ -54,7 +54,7 @@ commands = # A test environment that will run the PyLint linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples [testenv:pylint] -basepython = python3.9.21 +basepython = python3.13.3 commands = uv run pylint \ --rcfile="linters/.pylintrc" \ @@ -95,4 +95,3 @@ show_missing = true # called */source/corelay and */.tox/*/lib/python*/site-packages/corelay are equivalent [coverage:paths] source = */source/corelay,*/.tox/*/lib/python*/site-packages/corelay - From c4e47d200710f8655b356fa4092b2210d3973803 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Thu, 17 Apr 2025 18:00:59 +0200 Subject: [PATCH 09/19] Fixed the Unit Tests Some of the unit tests were failing due to changes in the dependencies, which were updated to fix the issues: - Since NumPy 1.26, NumPy array functions are no longer actual functions, instead they are now wrapped in a class, which calls into the C implementation of the corresponding functions. Unfortunately, this class does not implement "FunctionType". Many of the classes in CoRelAy allow a "dtype", which is checked for consistency with the input data. For example, the "Param" class allows to specify a "dtype" and a default value. The default value is checked for consistency with the "dtype" and an exception is raised if the default value is not of the correct type. Since NumPy array functions are no longer actual functions, code like "pooling_function = Param(FunctionType, np.sum)" cannot be used anymore. In fact, this code would also not work for builtin functions like "sum", for class methods, and some other cases. For this reason, the consistency check will now detect if the user specified "FunctionType" as the "dtype" and will automatically add the following types to the list of accepted types: "BuiltinFunctionType", "BuiltinMethodType", "MethodType", "numpy.ufunc", and "type(numpy.max)" (the type of NumPy array functions is private). This will make old code work as expected and open up new possibilities for the user, like using builtin functions and class methods. - Functions in SciKit Learn and SciKit Image that allow single-channel or multi-channel images used to have a boolean "multichannel" constructor parameter. This parameter was deprecated in favor of specifying the axis of the channels using a new "channel_axis" parameter. A value of "None" indicates that the image is single-channel. The usage of "multichannel" was removed and replaced with the appropriate "channel_axis" parameter. - The SciKit Learn implementation of the t-SNE dimensionality reduction algorithm, represented by the "TSNE" class, now uses PCA as the default initialization method instead of a random initialization. The "precomputed" metric is not compatible with PCA initialization, which was used in CoRelAy, which caused an exception to be raised. For this reason, the initialization method is now explicitly set to "random" in the "TSNE" class. - The "AgglomerativeClustering" class in SciKit Learn now uses the "metric" constructor parameter instead of "affinity" to specify the distance metric. Uses of the "affinity" parameter were replaced with the "metric" parameter. --- CHANGELOG.md | 5 +++ example/virelay_analysis.py | 2 +- source/corelay/plugboard.py | 40 +++++++++++++++++++++-- source/corelay/processor/clustering.py | 2 +- source/corelay/processor/embedding.py | 1 + source/corelay/processor/preprocessing.py | 2 +- tests/linters/cspell/.cspell.json | 2 ++ 7 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ab198..980b49d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,11 @@ - Support for Python 3.7 was removed, not only because it has already reached its end-of-life, but also because some of the dependencies (especially tox-uv) do not support it anymore. The two remaining supported Python versions (3.8 and 3.9) are now recorded in the `.python-versions` file, which makes it trivial to install them using uv. - Updated the Python dependencies in the `pyproject.toml` to their respective latest versions. - Some of the new dependency versions no longer support Python 3.8 and Python 3.9 (as well as Python 3.10). For this reason, the project was migrated to support Python 3.11, 3.12, and 3.13. The `.python-versions` file, the tox configuration, and the GitHub Actions workflow were updated to reflect this change. +- Some of the unit tests were failing due to changes in the dependencies, which were updated to fix the issues: + - Since NumPy 1.26, NumPy array functions are no longer actual functions, instead they are now wrapped in a class, which calls into the C implementation of the corresponding functions. Unfortunately, this class does not implement `FunctionType`. Many of the classes in CoRelAy allow a `dtype`, which is checked for consistency with the input data. For example, the `Param` class allows to specify a `dtype` and a default value. The default value is checked for consistency with the `dtype` and an exception is raised if the default value is not of the correct type. Since NumPy array functions are no longer actual functions, code like `pooling_function = Param(FunctionType, np.sum)` cannot be used anymore. In fact, this code would also not work for builtin functions like `sum`, for class methods, and some other cases. For this reason, the consistency check will now detect if the user specified `FunctionType` as the `dtype` and will automatically add the following types to the list of accepted types: `BuiltinFunctionType`, `BuiltinMethodType`, `MethodType`, `numpy.ufunc`, and `type(numpy.max)` (the type of NumPy array functions is private). This will make old code work as expected and open up new possibilities for the user, like using builtin functions and class methods. + - Functions in SciKit Learn and SciKit Image that allow single-channel or multi-channel images used to have a boolean `multichannel` constructor parameter. This parameter was deprecated in favor of specifying the axis of the channels using a new `channel_axis` parameter. A value of `None` indicates that the image is single-channel. The usage of `multichannel` was removed and replaced with the appropriate `channel_axis` parameter. + - The SciKit Learn implementation of the t-SNE dimensionality reduction algorithm, represented by the `TSNE` class, now uses PCA as the default initialization method instead of a random initialization. The `precomputed` metric is not compatible with PCA initialization, which was used in CoRelAy, which caused an exception to be raised. For this reason, the initialization method is now explicitly set to `random` in the `TSNE` class. + - The `AgglomerativeClustering` class in SciKit Learn now uses the `metric` constructor parameter instead of `affinity` to specify the distance metric. Uses of the `affinity` parameter were replaced with the `metric` parameter. - The unit tests were moved from the `tests` folder to `tests/unit_tests`, which clears up space for other test files. - The tox configuration was moved from the root directory to the `tests` directory, which is more appropriate, as tox is mostly used for testing and linting. - The configurations for the linters PyLint and Flake8 were moved from the root directory of the repository (in the case of PyLint) and from the tox configuration file (in the case of Flake8) into the `tests/linters` directory. The CSpell configuration was also moved there. diff --git a/example/virelay_analysis.py b/example/virelay_analysis.py index d391832..c6cb42b 100644 --- a/example/virelay_analysis.py +++ b/example/virelay_analysis.py @@ -69,7 +69,7 @@ def function(self, data): N, H, W = data.shape return squareform(pdist( data.reshape(N, H * W), - metric=lambda x, y: structural_similarity(x.reshape(H, W), y.reshape(H, W), multichannel=False) + metric=lambda x, y: structural_similarity(x.reshape(H, W), y.reshape(H, W)) )) diff --git a/source/corelay/plugboard.py b/source/corelay/plugboard.py index 50b1c04..22b56c0 100644 --- a/source/corelay/plugboard.py +++ b/source/corelay/plugboard.py @@ -1,4 +1,8 @@ """Plugboards are classes which contain Slots that are filled using Plugs.""" + +import numpy +from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, MethodType + from .tracker import Tracker @@ -61,6 +65,14 @@ def __init__(self, dtype=object, default=None, **kwargs): self.__name__ = '' self._consistent() + _function_types = ( + BuiltinFunctionType, + BuiltinMethodType, + MethodType, + numpy.ufunc, + type(numpy.max) + ) + def _consistent(self): """Check whether self.dtype and self.default are consistent, e.g. self.default is either None, or of type self.dtype. @@ -81,7 +93,15 @@ def _consistent(self): f"'{type(self).__name__}' object '{self.__name__}' default values '{self.dtype}' " "is neither a type, nor a tuple of types." ) - if self.default is not None and not isinstance(self.default, self.dtype): + + # If the user sets the dtype to FunctionType, then we should add some more types to check against, because many functions are not of type + # FunctionType, e.g., builtin functions, methods, and, since NumPy 1.26, NumPy array functions are no longer actual functions; so, for + # example, something like pooling_function = Param(FunctionType, np.sum) would not work anymore + effective_dtypes = self.dtype if isinstance(self.dtype, tuple) else (self.dtype,) + if FunctionType in effective_dtypes: + effective_dtypes = effective_dtypes + Slot._function_types + + if self.default is not None and not isinstance(self.default, effective_dtypes): raise TypeError( f"'{type(self).__name__}' object '{self.__name__}' default value is not of type '{self.dtype}'." ) @@ -276,6 +296,14 @@ def __init__(self, slot, obj=None, default=None, **kwargs): self._default = default self._consistent() + _function_types = ( + BuiltinFunctionType, + BuiltinMethodType, + MethodType, + numpy.ufunc, + type(numpy.max) + ) + def _consistent(self): """Checks whether all values are consistent, e.g. at least one of obj, default and fallback is set and of dtype slot.dtype. @@ -291,7 +319,15 @@ def _consistent(self): f"'{type(self.slot).__name__}' object '{self.slot.__name__}' is mandatory, " "yet it has been accessed without being set." ) - if not isinstance(self.obj, self.slot.dtype): + + # If the user sets the dtype to FunctionType, then we should add some more types to check against, because many functions are not of type + # FunctionType, e.g., builtin functions, methods, and, since NumPy 1.26, NumPy array functions are no longer actual functions; so, for + # example, something like pooling_function = Param(FunctionType, np.sum) would not work anymore + effective_dtypes = self.slot.dtype if isinstance(self.slot.dtype, tuple) else (self.slot.dtype,) + if FunctionType in effective_dtypes: + effective_dtypes = effective_dtypes + Plug._function_types + + if not isinstance(self.obj, effective_dtypes): raise TypeError( f"'{type(self.slot).__name__}' object '{self.slot.__name__}' value '{self.obj}' " f"is not of type '{self.slot.dtype}'." diff --git a/source/corelay/processor/clustering.py b/source/corelay/processor/clustering.py index 378a440..69f073c 100644 --- a/source/corelay/processor/clustering.py +++ b/source/corelay/processor/clustering.py @@ -133,7 +133,7 @@ class AgglomerativeClustering(Clustering): def function(self, data): # pylint: disable=not-a-mapping - clustering = sklearn.cluster.AgglomerativeClustering(n_clusters=self.n_clusters, affinity=self.metric, + clustering = sklearn.cluster.AgglomerativeClustering(n_clusters=self.n_clusters, metric=self.metric, linkage=self.linkage, **self.kwargs) return clustering.fit_predict(data) diff --git a/source/corelay/processor/embedding.py b/source/corelay/processor/embedding.py index 70612d7..04418ba 100644 --- a/source/corelay/processor/embedding.py +++ b/source/corelay/processor/embedding.py @@ -72,6 +72,7 @@ class TSNEEmbedding(Embedding): def function(self, data): # pylint: disable=not-a-mapping tsne = TSNE(n_components=self.n_components, + init='random', metric=self.metric, perplexity=self.perplexity, early_exaggeration=self.early_exaggeration, diff --git a/source/corelay/processor/preprocessing.py b/source/corelay/processor/preprocessing.py index 56abc37..65a63fc 100644 --- a/source/corelay/processor/preprocessing.py +++ b/source/corelay/processor/preprocessing.py @@ -163,7 +163,7 @@ def function(self, data): if self.channels_first: data = np.moveaxis(data, 1, -1) # pylint: disable=not-a-mapping - out = np.stack([skimage.transform.rescale(x, self.scale, order=self.filter, multichannel=multichannel, + out = np.stack([skimage.transform.rescale(x, self.scale, order=self.filter, channel_axis=-1 if multichannel else None, **self.kwargs) for x in data]) if self.channels_first: out = np.moveaxis(out, -1, 1) diff --git a/tests/linters/cspell/.cspell.json b/tests/linters/cspell/.cspell.json index a0dffba..6d0c0ed 100644 --- a/tests/linters/cspell/.cspell.json +++ b/tests/linters/cspell/.cspell.json @@ -184,6 +184,7 @@ "rodolphebarbanneau", "Samek", "skimage", + "SNE", "sphinxcontrib", "Sprincl", "sqeuclidean", @@ -203,6 +204,7 @@ "toxworkdir", "trange", "TSNE", + "ufunc", "umap", "vimrc", "vsicons", From 7562a8665f4735182b9dff5a34c613f98ca4b254 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Tue, 22 Apr 2025 14:13:59 +0200 Subject: [PATCH 10/19] Updated the Linting Updated the linting of the project. First of all, the Flake8 linter was removed. It is a wrapper for the PyFlakes and PyCodeStyle linters, and Ned Batchelder's McCabe script, which is used to compute the McCabe complexity. PyFlakes is a static code analyzer similar to PyLint, but way less useful. McCabe complexity is a useful metric, but it is not currently used by the project. For this reason, the Flake8 linter was removed and PyCodeStyle is now directly used instead. Also, MyPy, a static type checker for Python, PyDocLint, a docstring linter, and MarkdownLint, a linter for Markdown documents, were added to the project. The configuration for PyLint was updated by removing all options that only have their default values, as they are automatically set by PyLint. The remaining options were updated to make them less restrictive for the project. Mainly, maximum and minimum values were increased/decreased to allow for more flexibility. Flake8 was removed from the tox configuration and the GitHub Actions workflow, and the new linters were added. All errors and warnings from the linters were fixed. In particular, the following incomplete list of changes were made: - The imports were sorted in all Python files. They are now categorized by standard library imports, third-party library imports, and local imports, each separated by a blank line. Each category is now sub-categorized into regular imports and "from-imports". These are not separated by blank lines. Each sub-category is sorted alphabetically. - The docstrings were updated to now follow the Google style guide, which is easier to read and write than the NumPyDoc style. Also, this style is supported by PyDocLint, which is now used to lint the docstrings. - Missing module docstrings, function docstrings, class docstrings, and method docstrings were added to all Python files. - The maximum line length was set to 150 characters, which is a bit less restrictive than the previous 120 characters. Still, this should fit two files side by side on modern high-resolution monitors. - "Click" was removed as the command line argument parser. It is a great library, but it has the disadvantage of producing a lot of PyLint errors. Instead, the built-in "argparse" library is now used, which is a bit more verbose, but it does not produce any PyLint errors. - Variables were renamed to be more descriptive. Previously, most variable names were heavily abbreviated and some of them were not very intuitive. - Most of the inline PyLint disables were removed and either the offending rule was directly disabled or the code was changed to not trigger the rule anymore. This was done to make the code cleaner and easier to read. For all remaining inline PyLint disables, the reason for the disable was better explained. - Relative imports were replaced by absolute imports, because they make it easier to understand where the module is located. Especially because the project has multiple modules that are named the same in different sub-packages. - Type hints were added to all functions, methods, class attributes, and variables where necessary. Also, parameters and attributes of type string that had a specific set of possible values are now typed using the "Literal" type. This makes possible for MyPy to check if specified values are valid. - In the unit tests, the fixture parameters were masking the fixture function names. This was fixed by renaming the fixture function names "get__fixture" and specifying an explicit fixture name. The fixtures are now also explicitly scoped to the module level, although this did not cause any problems previously. In general, the code was cleaned up to make it easier to read, understand, and maintain. Some instances of dead code were eliminated. The goal was to make the code more Pythonic and to follow the PEP 8 style guide as closely as possible, while still maintaining backwards compatibility. This was, however, not possible in all cases and some backwards compatibility was sacrificed for the sake of improved static typing. This includes the removal of some meta classes, which were replaced by protocols (which are implicit interfaces). The most important change was how slots are defined. Previously, the slots were defined by assigning an instance of the "Slot" class (e.g., using the "Param" class, which derives from the "Slot" class) to a class attribute. The problem with this is, that Python allows users to access class attributes via the class name or via an instance of the class, e.g., using "self". This is unless an instance attribute with the same name is defined is defined, in which case the class attribute is accessed when using the class name and the instance attribute is accessed when using the class instance. The previous version of CoRelAy exploited this by returning the "Slot" instance when accessing the attribute via the class name and returning the value of the "Slot" when accessing the attribute via the class instance. Unfortunately, MyPy cannot handle this and therefore always expects the "Slot" instance to be returned when accessing the attribute in any way and thus raises an error because there is a type mismatch. This was fixed by introducing a new syntax, where slots are now defined via type hints. Slots are now defined by declaring a class attribute with a type hint using the "Annotated" type. This is a type that allows to specify a type and additional metadata. For example, a string "Param" can be defined as follows: "param: Annotated[str, Param(str, 'Default value')]". This way, MyPy can infer the runtime type of the attribute and the metadata is used to define the "Slot". The old syntax is still supported, but it is no longer recommended and may be removed in the future. The Markdown files in the project were also updated to follow the MarkdownLint style guide. A major problem in the tox configuration was fixed. Previously, tox-uv was always using the system Python version instead of the Python versions installed via uv. This meant that the Python versions specified in the environment names and the "base_python" option were ignored. This meant that the unit tests were not run with the correct Python version and the environments that used the "base_python" option were not created with the correct Python version. This was fixed by setting the "uv_python_preference" to "only-managed". Since tox-uv could not find the "pyproject.toml" file, the tox configuration file had to be moved to the "source" directory, which is the directory where the "pyproject.toml" file is located. Furthermore, as a general clean up, the PyTest and PyCoverage configurations were moved out of the tox configuration file and into their own configuration files, which were added to the "tests/unit_tests" directory. The configuration for Sphinx was not only updated to fix the linter warnings and generally to make it more readable, but also to fix errors that arose due to the fixes that were made to the tox configuration. The "docs" environment specified a base Python version of 3.13.3, but due to the mistakes that I had made in the tox configuration, tox-uv was not using the correct Python version, but the system Python version instead. Now, that the tox configuration was fixed, the correct Python version is used, which is 3.13.3. This caused some errors in the Sphinx configuration, because the "pkg_resources" module was deprecated and removed in Python 3.12. This module was used to get the source code file paths and line numbers of functions, classes, methods, etc. for the "linkcode" extension. This was replaced by the "inspect" module. Also, the configuration was updated to use the correct capitalization of the project name and to always use the current year in the copyright instead of hard-coding it. The GitHub Actions workflow was updated to now run when pull requests or merges are made to the "develop" branch. Previously, this was commented out, because the unit tests, the linters, and the building of the documentation were not working and would therefore fail the workflow. Now that all of them are working, the workflow was updated to run when pull requests or merges are made to the "develop" branch. In the "pyproject.toml" file, the "dev-dependencies" option of the "tool.uv" section was replaced by "dependency-groups", which have recently been standardized and are now supported by the uv project manager. Instead of only having a single "dev" group, a group for building the documentation, a group for running the linters, and a group for running the unit tests were created. The "dev" group includes all of the other groups, so that all dependencies are installed when the "dev" group is installed. Also, the extra requirements were accidentally not carried over from the old "setup.py" file to the new "pyproject.toml" file. These were added again to the "project.optional-dependencies" section. The "docs" and "tests" groups were not added to the optional dependencies, because they are now included in the dependency groups. A note was added to the read me file, explaining how to install the development dependencies including all optional dependencies. Finally, the license identifier that was used in the citation file was wrong and was changed from "AGPL-3.0-or-later" to "GPL-3.0-or-later AND LGPL-3.0-or-later". --- .github/workflows/tests.yml | 93 +- .gitignore | 30 +- CHANGELOG.md | 63 +- CITATION.cff | 2 +- README.md | 172 +- docs/source/conf.py | 398 +++- docs/source/getting-started.rst | 210 ++- example/corelay_basics.py | 139 +- example/hdf5_structure.py | 376 ++-- example/memoize_spectral_pipeline.py | 131 +- example/virelay_analysis.py | 490 +++-- source/corelay/__init__.py | 4 + source/corelay/base.py | 79 +- source/corelay/io/__init__.py | 13 +- source/corelay/io/hashing.py | 139 +- source/corelay/io/storage.py | 806 +++++++-- source/corelay/pipeline/__init__.py | 2 +- source/corelay/pipeline/base.py | 377 ++-- source/corelay/pipeline/spectral.py | 106 +- source/corelay/plugboard.py | 805 +++++---- source/corelay/processor/__init__.py | 2 +- source/corelay/processor/affinity.py | 136 +- source/corelay/processor/base.py | 352 ++-- source/corelay/processor/clustering.py | 459 +++-- source/corelay/processor/distance.py | 86 +- source/corelay/processor/embedding.py | 358 +++- source/corelay/processor/flow.py | 260 ++- source/corelay/processor/laplacian.py | 112 +- source/corelay/processor/preprocessing.py | 362 ++-- source/corelay/py.typed | 0 source/corelay/tracker.py | 261 ++- source/corelay/utils.py | 286 ++- source/pyproject.toml | 64 +- source/tox.ini | 120 ++ source/uv.lock | 1607 ++++++++++------- tests/linters/.flake8 | 16 - tests/linters/.mypy.ini | 40 + tests/linters/.pycodestyle | 7 + tests/linters/.pydoclint.toml | 15 + tests/linters/.pylintrc | 529 +----- tests/linters/cspell/.cspell.json | 32 + tests/linters/cspell/package.json | 2 +- tests/linters/markdownlint/.markdownlint.yaml | 19 + tests/linters/markdownlint/package-lock.json | 1174 ++++++++++++ tests/linters/markdownlint/package.json | 18 + tests/tox.ini | 97 - tests/unit_tests/.coveragerc | 31 + tests/unit_tests/.pytest.ini | 12 + tests/unit_tests/corelay/__init__.py | 1 + tests/unit_tests/corelay/io/__init__.py | 1 + tests/unit_tests/corelay/io/test_storage.py | 233 ++- tests/unit_tests/corelay/pipeline/__init__.py | 1 + .../unit_tests/corelay/pipeline/test_base.py | 290 ++- .../corelay/pipeline/test_spectral.py | 349 ++-- .../unit_tests/corelay/processor/__init__.py | 1 + .../unit_tests/corelay/processor/test_base.py | 471 +++-- .../corelay/processor/test_clustering.py | 151 +- .../corelay/processor/test_embedding.py | 101 +- .../unit_tests/corelay/processor/test_flow.py | 93 +- .../corelay/processor/test_preprocessing.py | 190 +- tests/unit_tests/corelay/test_base.py | 38 +- tests/unit_tests/corelay/test_plugboard.py | 550 ++++-- tests/unit_tests/corelay/test_tracker.py | 131 +- tests/unit_tests/corelay/test_utils.py | 67 +- 64 files changed, 9065 insertions(+), 4495 deletions(-) create mode 100644 source/corelay/py.typed create mode 100644 source/tox.ini delete mode 100644 tests/linters/.flake8 create mode 100644 tests/linters/.mypy.ini create mode 100644 tests/linters/.pycodestyle create mode 100644 tests/linters/.pydoclint.toml create mode 100644 tests/linters/markdownlint/.markdownlint.yaml create mode 100644 tests/linters/markdownlint/package-lock.json create mode 100644 tests/linters/markdownlint/package.json delete mode 100644 tests/tox.ini create mode 100644 tests/unit_tests/.coveragerc create mode 100644 tests/unit_tests/.pytest.ini diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 259de44..ecaea26 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,11 +7,11 @@ on: push: branches: - main - # - develop + - develop pull_request: branches: - main - # - develop + - develop # This workflow contains multiple jobs for unit testing, linting, type checking, spell checking, and building jobs: @@ -49,7 +49,7 @@ jobs: - name: Install CoRelAy and its Dependencies run: uv --directory source sync --all-extras --dev - name: Run Unit Tests - run: uv --directory source run tox --conf ../tests/tox.ini run -e ${{ matrix.tox-environment }} + run: uv --directory source run tox run -e ${{ matrix.tox-environment }} # Runs the PyLint linter pylint: @@ -73,17 +73,17 @@ jobs: - name: Install CoRelAy and its Dependencies run: uv --directory source sync --all-extras --dev - name: Run PyLint Linter - run: uv --directory source run tox --conf ../tests/tox.ini run -e pylint + run: uv --directory source run tox run -e pylint - # Runs the Flake8 linter - flake8: + # Runs the PyCodeStyle linter + pycodestyle: # The job will run on the latest version of Ubuntu - name: Lint CoRelAy Using Flake8 + name: Lint CoRelAy Using PyCodeStyle runs-on: ubuntu-latest # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct - # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and 5) running the Flake8 linter + # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and 5) running the PyCodeStyle linter steps: - uses: actions/checkout@v4 with: @@ -96,8 +96,79 @@ jobs: run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies run: uv --directory source sync --all-extras --dev - - name: Run Flake8 Linter - run: uv --directory source run tox --conf ../tests/tox.ini run -e flake8 + - name: Run PyLint Linter + run: uv --directory source run tox run -e pycodestyle + + # Runs the PyDocLint docstring linter + pydoclint: + + # The job will run on the latest version of Ubuntu + name: Lint CoRelAy Docstrings Using PyDocLint + runs-on: ubuntu-latest + + # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct + # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and 5) running the PyDocLint docstring linter + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.6.14 + - name: Install Python + run: uv python install 3.13.3 + - name: Install CoRelAy and its Dependencies + run: uv --directory source sync --all-extras --dev + - name: Run PyLint Linter + run: uv --directory source run tox run -e pydoclint + + # Runs the MyPy static type checker + mypy: + + # The job will run on the latest version of Ubuntu + name: Static Type-Check CoRelAy Using MyPy + runs-on: ubuntu-latest + + # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing the correct + # Python version, 4) setting up the test environment by installing CoRelAy and its dependencies, and 5) running the MyPy static type checker + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.6.14 + - name: Install Python + run: uv python install 3.13.3 + - name: Install CoRelAy and its Dependencies + run: uv --directory source sync --all-extras --dev + - name: Run PyLint Linter + run: uv --directory source run tox run -e mypy + + # Runs the Markdownlint linter + markdownlint: + + # The job will run on the latest version of Ubuntu + name: Lint CoRelAy Using Markdownlint + runs-on: ubuntu-latest + + # The job contains several steps: 1) checking out the repository, 2) installing Node.js, 3) installing the dependencies of the Markdownlint + # global configuration, and 4) running the Markdownlint linter + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + - name: Install Markdownlint Global Configuration + run: npm --prefix tests/linters/markdownlint install + - name: Run Markdown Linter + run: npm --prefix tests/linters/markdownlint run markdownlint # Runs the spell checker CSpell to spell-check all files in the repository spell-check: @@ -149,4 +220,4 @@ jobs: - name: Install TeX Live for Pybtex run: sudo apt-get update -y && sudo apt-get install -y texlive texlive-latex-extra dvipng - name: Build Documentation - run: uv --directory source run tox --conf ../tests/tox.ini run --notest -e docs + run: uv --directory source run tox run --notest -e docs diff --git a/.gitignore b/.gitignore index acc838d..0f7403b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,30 +4,28 @@ Thumbs.db # IDE and code editor configurations +.exrc +.idea +.vimrc .vs/ .vscode/ -.idea *.sublime-workspace -.exrc -.vimrc # Ignores Python virtual environments .*venv/ # Build output __pycache__/ -source/dist/ source/corelay/version.py +source/dist/ # MyPy type information cache -.mypy_cache +.mypy_cache/ # Testing and coverage output -.tox -.coverage -.coverage.* -.pytest_cache -htmlcov +.pytest_cache/ +.tox/ +coverage/ # Dependencies of the frontend installed via NPM node_modules @@ -38,19 +36,19 @@ npm-debug.log .htmlvalidate.js # Documentation build output and autogenerated files -docs/build -docs/doctree -docs/source/_generated +docs/build/ +docs/doctree/ +docs/source/_generated/ docs/source/reference/* !docs/source/reference/index.rst # Trained models and datasets/attributions/analyses -*.pt *.h5 *.hdf5 -project.yaml +*.pt label-map.json -test-project +project.yaml +test-project/ # To do lists TODO.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 980b49d..6aa76db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ - Renamed the `master` branch to `main` in order to avoid any links to sensitive topics. All references to the `master` branch in the repository were updated to `main`. - Added this changelog, as well as a contributors list, which contains a list of all people that made contributions to the project. -- Added a CSpell configuration for spell-checking the contents of the repository, checked all files, and corrected all spelling mistakes. +- CSpell, a spell-checker, was added to the project. The contents of the repository, including the source code, were spell-checked and all spelling mistakes were corrected. +- Markdownlint, a linter for Markdown documents, was added to the project and all Markdown files in the project were updated to follow the MarkdownLint style guide. - Moved the logo and its source file from the docs to a separate top-level `design` directory: - The source file was cleaned up: - Converted the title of the logo to a path, because the font is from Google Fonts and not available in the SVG. It would be possible to embed the font, but this would increase the size of the SVG significantly and would require us to include the license. @@ -18,7 +19,6 @@ - All references to the old logo were updated to point to the new location. The URL used in the read me was made absolute, because the read me is also used for the PyPI package and PyPI would not be able to resolve the relative URL to the logo on GitHub. - The project is dual-licensed under the GNU General Public License Version 3 (GPL-3.0) or later, and the GNU Lesser General Public License Version 3 (LGPL-3.0) or later. The GPL-3.0 license is in the `COPYING` file and the LGPL-3.0 license is in the `COPYING.LESSER` file. Additionally, there used to be a `LICENSE` file, which contained a note about the dual-licensing. This was, however, confusing, as GitHub does not recognize that the file is only a note about the dual-licensing and not the actual license. The `LICENSE` file was removed and the note about the dual-licensing was added to the read me. - Added a `CITATION.cff` file, which contains the necessary information to cite this repository. This file is based on the [Citation File Format (CFF)](https://citation-file-format.github.io) standard. This file is supported by GitHub and results in a "Cite this repository" button on the website, which allows users to directly generate a proper citation for the repository in multiple different formats. -- The configuration for the GitLab CI, which was stored in the `.gitlab-ci.yml` file, was removed. The project is no longer being hosted on GitLab, and the CI configuration is no longer needed. ### CoRelAy Updates in v0.3.0 @@ -27,32 +27,65 @@ - The source code was moved from `src/corelay` to `source/corelay`. - The tox configuration was updated and now uses tox-uv to run all commands via uv instead of directly creating environments. This means, that all Python environments can now be run without having to install multiple Python versions. - The tox configuration was also cleaned up. - - Support for Python 3.7 was removed, not only because it has already reached its end-of-life, but also because some of the dependencies (especially tox-uv) do not support it anymore. The two remaining supported Python versions (3.8 and 3.9) are now recorded in the `.python-versions` file, which makes it trivial to install them using uv. -- Updated the Python dependencies in the `pyproject.toml` to their respective latest versions. -- Some of the new dependency versions no longer support Python 3.8 and Python 3.9 (as well as Python 3.10). For this reason, the project was migrated to support Python 3.11, 3.12, and 3.13. The `.python-versions` file, the tox configuration, and the GitHub Actions workflow were updated to reflect this change. -- Some of the unit tests were failing due to changes in the dependencies, which were updated to fix the issues: - - Since NumPy 1.26, NumPy array functions are no longer actual functions, instead they are now wrapped in a class, which calls into the C implementation of the corresponding functions. Unfortunately, this class does not implement `FunctionType`. Many of the classes in CoRelAy allow a `dtype`, which is checked for consistency with the input data. For example, the `Param` class allows to specify a `dtype` and a default value. The default value is checked for consistency with the `dtype` and an exception is raised if the default value is not of the correct type. Since NumPy array functions are no longer actual functions, code like `pooling_function = Param(FunctionType, np.sum)` cannot be used anymore. In fact, this code would also not work for builtin functions like `sum`, for class methods, and some other cases. For this reason, the consistency check will now detect if the user specified `FunctionType` as the `dtype` and will automatically add the following types to the list of accepted types: `BuiltinFunctionType`, `BuiltinMethodType`, `MethodType`, `numpy.ufunc`, and `type(numpy.max)` (the type of NumPy array functions is private). This will make old code work as expected and open up new possibilities for the user, like using builtin functions and class methods. - - Functions in SciKit Learn and SciKit Image that allow single-channel or multi-channel images used to have a boolean `multichannel` constructor parameter. This parameter was deprecated in favor of specifying the axis of the channels using a new `channel_axis` parameter. A value of `None` indicates that the image is single-channel. The usage of `multichannel` was removed and replaced with the appropriate `channel_axis` parameter. - - The SciKit Learn implementation of the t-SNE dimensionality reduction algorithm, represented by the `TSNE` class, now uses PCA as the default initialization method instead of a random initialization. The `precomputed` metric is not compatible with PCA initialization, which was used in CoRelAy, which caused an exception to be raised. For this reason, the initialization method is now explicitly set to `random` in the `TSNE` class. - - The `AgglomerativeClustering` class in SciKit Learn now uses the `metric` constructor parameter instead of `affinity` to specify the distance metric. Uses of the `affinity` parameter were replaced with the `metric` parameter. -- The unit tests were moved from the `tests` folder to `tests/unit_tests`, which clears up space for other test files. -- The tox configuration was moved from the root directory to the `tests` directory, which is more appropriate, as tox is mostly used for testing and linting. -- The configurations for the linters PyLint and Flake8 were moved from the root directory of the repository (in the case of PyLint) and from the tox configuration file (in the case of Flake8) into the `tests/linters` directory. The CSpell configuration was also moved there. + - Support for Python 3.7, 3.8, and 3.9 were removed. Python 3.7 and 3.8 have already reached their end-of-life, and Python 3.9 is about to reach its end-of-life and already only receives security updates. Besides their support status, some of the dependencies (especially tox-uv) do not support them anymore. Even Python 3.10 is already unsupported by some of the dependencies. For this reason, only Python 3.11, 3.12, and 3.13 are supported now. They are recorded in the `.python-versions` file, which makes it trivial to install them using uv. +- Updated the Python dependencies: + - The Python dependencies specified in the `pyproject.toml` file were updated to their respective latest versions. +- Updated the unit tests: + - Some of the unit tests were failing due to changes in the dependencies, which were updated to fix the issues: + - Since NumPy 1.26, NumPy array functions are no longer actual functions, instead they are now wrapped in a class, which calls into the C implementation of the corresponding functions. Unfortunately, this class does not implement `FunctionType`. Many of the classes in CoRelAy allow a `dtype`, which is checked for consistency with the input data. For example, the `Param` class allows to specify a `dtype` and a default value. The default value is checked for consistency with the `dtype` and an exception is raised if the default value is not of the correct type. Since NumPy array functions are no longer actual functions, code like `pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)]` cannot be used anymore. In fact, this code would also not work for builtin functions like `sum`, for class methods, and some other cases. For this reason, the consistency check will now detect if the user specified `FunctionType` as the `dtype` and will automatically add the following types to the list of accepted types: `BuiltinFunctionType`, `BuiltinMethodType`, `MethodType`, `numpy.ufunc`, and `type(numpy.max)` (the type of NumPy array functions is private). This will make old code work as expected and open up new possibilities for the user, like using builtin functions and class methods. + - Functions in SciKit Learn and SciKit Image that allow single-channel or multi-channel images used to have a boolean `multichannel` constructor parameter. This parameter was deprecated in favor of specifying the axis of the channels using a new `channel_axis` parameter. A value of `None` indicates that the image is single-channel. The usage of `multichannel` was removed and replaced with the appropriate `channel_axis` parameter. + - The SciKit Learn implementation of the t-SNE dimensionality reduction algorithm, represented by the `TSNE` class, now uses PCA as the default initialization method instead of a random initialization. The `precomputed` metric is not compatible with PCA initialization, which was used in CoRelAy, which caused an exception to be raised. For this reason, the initialization method is now explicitly set to `random` in the `TSNE` class. + - The `AgglomerativeClustering` class in SciKit Learn now uses the `metric` constructor parameter instead of `affinity` to specify the distance metric. Uses of the `affinity` parameter were replaced with the `metric` parameter. + - The unit tests were moved from the `tests` folder to `tests/unit_tests`, which clears up space for other test files. +- Updated the linting of the project: + - First of all, the Flake8 linter was removed. It is a wrapper for the PyFlakes and PyCodeStyle linters, and Ned Batchelder's McCabe script, which is used to compute the McCabe complexity. PyFlakes is a static code analyzer similar to PyLint, but way less useful. McCabe complexity is a useful metric, but it is not currently used by the project. For this reason, the Flake8 linter was removed and PyCodeStyle is now directly used instead. + - Also, MyPy, a static type checker for Python, and PyDocLint, a docstring linter, were added to the project. The configuration for PyLint was updated by removing all options that only have their default values, as they are automatically set by PyLint. The remaining options were updated to make them less restrictive for the project. Mainly, maximum and minimum values were increased/decreased to allow for more flexibility. + - Flake8 was removed from the tox configuration and the GitHub Actions workflow, and the new linters were added. + - The configurations for the linters now reside in the `tests/linters` directory. The existing PyLint configuration was moved there. + - All errors and warnings from the linters were fixed. In particular, the following incomplete list of changes were made: + - The imports were sorted in all Python files. They are now categorized by standard library imports, third-party library imports, and local imports, each separated by a blank line. Each category is now sub-categorized into regular imports and "from-imports". These are not separated by blank lines. Each sub-category is sorted alphabetically. + - The docstrings were updated to now follow the Google style guide, which is easier to read and write than the NumPyDoc style. Also, this style is supported by PyDocLint, which is now used to lint the docstrings. + - Missing module docstrings, function docstrings, class docstrings, and method docstrings were added to all Python files. + - The maximum line length was set to 150 characters, which is a bit less restrictive than the previous 120 characters. Still, this should fit two files side by side on modern high-resolution monitors. + - `Click` was removed as the command line argument parser. It is a great library, but it has the disadvantage of producing a lot of PyLint errors. Instead, the built-in `argparse` library is now used, which is a bit more verbose, but it does not produce any PyLint errors. + - Variables were renamed to be more descriptive. Previously, most variable names were heavily abbreviated and some of them were not very intuitive. + - Most of the inline PyLint disables were removed and either the offending rule was directly disabled or the code was changed to not trigger the rule anymore. This was done to make the code cleaner and easier to read. For all remaining inline PyLint disables, the reason for the disable was better explained. + - Relative imports were replaced by absolute imports, because they make it easier to understand where the module is located. Especially because the project has multiple modules that are named the same in different sub-packages. + - Type hints were added to all functions, methods, class attributes, and variables where necessary. Also, parameters and attributes of type string that had a specific set of possible values are now typed using the `Literal` type. This makes possible for MyPy to check if specified values are valid. + - In the unit tests, the fixture parameters were masking the fixture function names. This was fixed by renaming the fixture function names `get__fixture` and specifying an explicit fixture name. The fixtures are now also explicitly scoped to the module level, although this did not cause any problems previously. +- The tox configuration was moved from the root directory to the `source` directory, which is more appropriate, as tox is mostly used for building, testing, and linting the project and should therefore be in the same directory as the rest of the project files. Also, since tox now uses uv, it needs to be in the same directory as the `pyproject.toml` file in order to be able to install dependencies. - The `corelay/version.py` file was deleted as it is automatically generated during the build process and should not be checked into source control. +- In general, the code was cleaned up to make it easier to read, understand, and maintain. Some instances of dead code were eliminated. The goal was to make the code more Pythonic and to follow the PEP 8 style guide as closely as possible, while still maintaining backwards compatibility. This was, however, not possible in all cases and some backwards compatibility was sacrificed for the sake of improved static typing. This includes: + - Some meta classes were removed and replaced by protocols (which are implicit interfaces). + - The most important change was how slots are defined: + - Previously, the slots were defined by assigning an instance of the `Slot` class (e.g., using the `Param` class, which derives from the `Slot` class) to a class attribute. + - The problem with this is, that Python allows users to access class attributes via the class name or via an instance of the class, e.g., using `self`. This is unless an instance attribute with the same name is defined is defined, in which case the class attribute is accessed when using the class name and the instance attribute is accessed when using the class instance. + - The previous version of CoRelAy exploited this by returning the `Slot` instance when accessing the attribute via the class name and returning the value of the `Slot` when accessing the attribute via the class instance. + - Unfortunately, MyPy cannot handle this and therefore always expects the `Slot` instance to be returned when accessing the attribute in any way and thus raises an error because there is a type mismatch. + - This was fixed by introducing a new syntax, where slots are now defined via type hints. + - Slots are now defined by declaring a class attribute with a type hint using the `Annotated` type. This is a type that allows to specify a type and additional metadata. For example, a string `Param` can be defined as follows: `param: Annotated[str, Param(str, 'Default value')]`. + - This way, MyPy can infer the runtime type of the attribute and the metadata is used to define the `Slot`. + - The old syntax is still supported, but it is no longer recommended and may be removed in the future. ### CI/CD Updates in v0.3.0 - The GitHub Actions workflow was updated to point to the new locations of the source code, unit tests, and configuration files. - Converted the GitHub Actions workflow to use uv to run the tests, linters, and build the documentation. - The GitHub Actions workflow was cleaned up and documented. -- Split up the GitHub Actions workflow job that ran PyLint and Flake8 into two separate jobs, which allows for better parallelization and faster execution of the workflow. +- Split up the GitHub Actions workflow job that ran the linters into separate jobs, which allows for better parallelization and faster execution of the workflow. - Updated the `actions/checkout` action used in the GitHub Actions workflow from version 2 to version 4. - Removed the GitHub Actions workflow matrix configurations for Python 3.7, as it is no longer supported by the project. - Added a job to the GitHub Actions workflow, which spell-checks the repository. +- The GitHub Actions workflow now runs on pull requests and merges to the `main` and the `develop` branches. The workflow was previously only ran on pull requests and merges to the `main` branch. This was changed, because every feature branch that is to be merged into `develop` should be tested and linted before it is merged. Otherwise, build, test, and linting errors would only be detected just before the release of a new version, when the `develop` branch is merged into `main`. +- The configuration for the GitLab CI, which was stored in the `.gitlab-ci.yml` file, was removed. The project is no longer being hosted on GitLab, and the CI configuration is no longer needed. ### Documentation Updates in v0.3.0 - The configuration for Read the Docs was updated to use the latest available versions of Ubuntu (24.04) and Python (3.12). It was also documented. +- The configuration for Sphinx was updated: + - All warnings and errors that were reported by the linters were fixed and the configuration generally cleaned up to make it more readable and maintainable. + - The `docs` environment in the tox configuration now uses Python 3.13.3, which causes an error during the building of the documentation, because the `pkg_resources` module was deprecated and removed in Python 3.12. This module was used to get the source code file paths and line numbers of functions, classes, methods, etc. for the `linkcode` extension. This was replaced by the `inspect` module. + - The correct capitalization of the project name is now specified in the Sphinx configuration and the year in the copyright is now always set to the current year instead of hard-coding it. - The logo was added to the documentation, which previously contained a copy of the logo in the `docs/images` directory, but did not include it. The version contained in the `docs/images` directory was removed and the index page now directly references the logo in the `design` directory. - The favicon used in the documentation was updated to use the SVG version of the logo without the title. Previously, it was still the old "S" logo, which was from before CoRelAy was renamed from Sprincl. - One of the new dependency versions (`metrohash-python`) requires a C++ compiler to build and uses the `c++` command, which may not be available on all systems. To ensure that users are not confused by this, a note was added to the read me file explaining that the `c++` command is required to build the project and showing how to install it on Fedora (one of the systems that do not have the `c++` command installed by default). @@ -134,5 +167,5 @@ - Added a reference to the ClArC paper with a link and BibTeX in the read me. - Fixed some minor problems in the examples: - The imports from `sprincl` were fixed to imports from `corelay`. - - SciKit-Image was updated and the usage of the `compare` function from `skimage.measure` was updated to the `structural_similarity` from `skimage.metrics`. + - SciKit-Image was updated and the usage of the `compare` function from `skimage.measure` was updated to the `structural_similarity` function from `skimage.metrics`. - Updated the paper in the read me from the old paper to the new paper. diff --git a/CITATION.cff b/CITATION.cff index 9e86639..2100c0e 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -64,4 +64,4 @@ keywords: - Zennit - CoRelAy - ViRelAy -license: AGPL-3.0-or-later +license: GPL-3.0-or-later AND LGPL-3.0-or-later diff --git a/README.md b/README.md index 065afc7..8f73b0d 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,26 @@ -# CoRelAy – Composing Relevance Analysis -![CoRelAy Logo](https://raw.githubusercontent.com/virelay/corelay/refs/heads/main/design/corelay-logo-with-title.png) + + + + + + +
+ +CoRelAy Logo + +# Composing Relevance Analysis -[![Documentation Status](https://readthedocs.org/projects/corelay/badge/?version=latest)](https://corelay.readthedocs.io/en/latest/?badge=latest) -[![tests](https://github.com/virelay/corelay/actions/workflows/tests.yml/badge.svg)](https://github.com/virelay/corelay/actions/workflows/tests.yml) -[![PyPI Version](https://img.shields.io/pypi/v/corelay)](https://pypi.org/project/corelay/) [![License](https://img.shields.io/pypi/l/corelay)](https://github.com/virelay/corelay/blob/main/COPYING.LESSER) +[![GitHub Actions Workflow Status](https://github.com/virelay/corelay/actions/workflows/tests.yml/badge.svg)](https://github.com/virelay/corelay/actions/workflows/tests.yml) +[![Documentation Status](https://readthedocs.org/projects/corelay/badge?version=latest)](https://corelay.readthedocs.io/en/latest) +[![GitHub Release](https://img.shields.io/github/v/release/virelay/corelay)](https://github.com/virelay/corelay/releases/latest) +[![PyPI Package](https://img.shields.io/pypi/v/corelay)](https://pypi.org/project/corelay/) -CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (Task) with default operations (Processor). Any step of the pipeline may then be individually changed by assigning a new operator (Processor). Processors have Params which define their operation. +**CoRelAy** is a tool to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (`Task`) with default operations (`Processor`). Any step of the pipeline may then be individually changed by assigning a new operator (`Processor`). Processors have parameters (`Params`) which define their operation. -CoRelAy was created to quickly implement pipelines to generate analysis data which can then be visualized using ViRelAy. +**CoRelAy** was created to quickly implement pipelines to generate analysis data which can then be visualized using **ViRelAy**. +
If you find CoRelAy useful for your research, why not cite our related [paper](https://arxiv.org/abs/2106.13200): @@ -56,78 +67,122 @@ $ pip install corelay[umap,hdbscan] Examples to highlight some features of **CoRelAy** can be found in `example/`. -We mainly use HDF5 files to store results. The structure used by **ViRelAy** is documented in the **ViRelAy** -repository at `docs/database_specification.md`. An example to create HDF5 files which can be used with **ViRelAy** is -shown in `example/hdf5_structure.py` +We mainly use HDF5 files to store results. The structure used by **ViRelAy** is documented in the **ViRelAy** repository at `docs/database_specification.md`. An example to create HDF5 files which can be used with **ViRelAy** is shown in `example/hdf5_structure.py` -To do a full SpRAy analysis which can be visualized with **ViRelAy**, an advanced script can be found in -`example/virelay_analysis.py`. +To do a full SpRAy analysis which can be visualized with **ViRelAy**, an advanced script can be found in `example/virelay_analysis.py`. The following shows the contents of `example/memoize_spectral_pipeline.py`: ```python -'''Example using memoization to store (intermediate) results.''' +"""An example script, which uses memoization to store (intermediate) results.""" + import time +from collections.abc import Sequence +from typing import Annotated, Any, SupportsIndex import h5py -import numpy as np +import numpy +from numpy.typing import NDArray from corelay.base import Param -from corelay.processor.base import Processor -from corelay.processor.flow import Sequential, Parallel +from corelay.io.storage import HashedHDF5 from corelay.pipeline.spectral import SpectralClustering +from corelay.processor.base import Processor from corelay.processor.clustering import KMeans from corelay.processor.embedding import TSNEEmbedding, EigenDecomposition -from corelay.io.storage import HashedHDF5 +from corelay.processor.flow import Sequential, Parallel -# custom processors can be implemented by defining a function attribute class Flatten(Processor): - def function(self, data): - return data.reshape(data.shape[0], np.prod(data.shape[1:])) + """Represents a CoRelAy processor, which flattens its input data.""" + + def function(self, data: Any) -> Any: + """Applies the flattening to the input data. + + Args: + data (Any): The input data that is to be flattened. + + Returns: + Any: Returns the flattened data. + """ + + input_data: NDArray[Any] = data + input_data.sum() + return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) class SumChannel(Processor): - # parameters can be assigned by defining a class-owned Param instance - axis = Param(int, 1) - def function(self, data): - return data.sum(1) + """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" + + def function(self, data: Any) -> Any: + """Applies the summation over the channels to the input data. + + Args: + data (Any): The input data that is to be summed over its channels. + + Returns: + Any: Returns the data that was summed up over its channels. + """ + + input_data: NDArray[Any] = data + return input_data.sum(axis=1) class Normalize(Processor): - def function(self, data): - data = data / data.sum((1, 2), keepdims=True) - return data + """Represents a CoRelAy processor, which normalizes its input data.""" + + axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] + """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" + def function(self, data: Any) -> Any: + """Normalizes the specified input data. -def main(): - np.random.seed(0xDEADBEEF) - fpath = 'test.analysis.h5' - with h5py.File(fpath, 'a') as fd: - # HashedHDF5 is an io-object that stores outputs of Processors based on hashes in hdf5 - iobj = HashedHDF5(fd.require_group('proc_data')) + Args: + data (Any): The input data that is to be normalized. - # generate some exemplary data - data = np.random.normal(size=(64, 3, 32, 32)) - n_clusters = range(2, 20) + Returns: + Any: Returns the normalized input data. + """ - # SpectralClustering is an Example for a pre-defined Pipeline + input_data: NDArray[Any] = data + return input_data / input_data.sum(self.axes, keepdims=True) + + +def main() -> None: + """The entrypoint to the memoize_spectral_pipeline script.""" + + # Fixes the random seed for reproducibility + numpy.random.seed(0xDEADBEEF) + + # Opens an HDF5 file in append mode for the storing the results of the analysis and the memoization of intermediate pipeline results + with h5py.File('test.analysis.h5', 'a') as analysis_file: + + # Creates a HashedHDF5 IO object, which is an IO object that stores outputs of processors based on hashes in an HDF5 file + io_object = HashedHDF5(analysis_file.require_group('proc_data')) + + # Generates some exemplary data + data = numpy.random.normal(size=(64, 3, 32, 32)) + number_of_clusters = range(2, 20) + + # Creates a SpectralClustering pipeline, which is one of the pre-defined built-in pipelines pipeline = SpectralClustering( - # processors, such as EigenDecomposition, can be assigned to pre-defined tasks - embedding=EigenDecomposition(n_eigval=8, io=iobj), - # flow-based Processors, such as Parallel, can combine multiple Processors - # broadcast=True copies the input as many times as there are Processors - # broadcast=False instead attempts to match each input to a Processor + + # Processors, such as EigenDecomposition, can be assigned to pre-defined tasks + embedding=EigenDecomposition(n_eigval=8, io=io_object), + + # Flow-based processors, such as Parallel, can combine multiple processors; broadcast=True copies the input as many times as there are + # processors; broadcast=False instead attempts to match each input to a processor clustering=Parallel([ Parallel([ - KMeans(n_clusters=k, io=iobj) for k in n_clusters + KMeans(n_clusters=k, io=io_object) for k in number_of_clusters ], broadcast=True), - # io-objects will be used during computation when supplied to Processors - # if a corresponding output value (here identified by hashes) already exists, - # the value is not computed again but instead loaded from the io object - TSNEEmbedding(io=iobj) + + # IO objects will be used during computation when supplied to processors, if a corresponding output value (here identified by hashes) + # already exists, the value is not computed again but instead loaded from the IO object + TSNEEmbedding(io=io_object) ], broadcast=True, is_output=True) ) + # Processors (and Params) can be updated by simply assigning corresponding attributes pipeline.preprocessing = Sequential([ SumChannel(), @@ -135,16 +190,13 @@ def main(): Flatten() ]) + # Processors flagged with "is_output=True" will be accumulated in the output; the output will be a tree of tuples, with the same hierarchy as + # the pipeline (i.e., _clusterings here contains a tuple of the k-means outputs) start_time = time.perf_counter() + _clusterings, _tsne = pipeline(data) - # Processors flagged with "is_output=True" will be accumulated in the output - # the output will be a tree of tuples, with the same hierarchy as the pipeline - # (i.e. clusterings here contains a tuple of the k-means outputs) - clusterings, tsne = pipeline(data) - - # since we memoize our results in a hdf5 file, subsequent calls will not compute - # the values (for the same inputs), but rather load them from the hdf5 file - # try running the script multiple times + # Since we memoize our results in an HDF5 file, subsequent calls will not compute the values (for the same inputs), but rather load them from + # the HDF5 file; try running the script multiple times duration = time.perf_counter() - start_time print(f'Pipeline execution time: {duration:.4f} seconds') @@ -153,6 +205,16 @@ if __name__ == '__main__': main() ``` +## Contributing + +TODO: Add a section on how to contribute to the project. + +Installing the development dependencies can be done using the following command: + +```shell +$ uv --directory source sync --all-extras +``` + ## License CoRelAy is dual-licensed under the [GNU General Public License Version 3 (GPL-3.0)](https://www.gnu.org/licenses/gpl-3.0.html) or later, and the [GNU Lesser General Public License Version 3 (LGPL-3.0)](https://www.gnu.org/licenses/lgpl-3.0.html) or later. For more information see the [GPL-3.0](https://github.com/virelay/corelay/blob/main/COPYING) and [LGPL-3.0](https://github.com/virelay/corelay/blob/main/COPYING.LESSER) license files. diff --git a/docs/source/conf.py b/docs/source/conf.py index c81d13a..804112a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,143 +1,349 @@ -import sys +"""Represents the configuration for Sphinx, which is executed at build time and can be used to configure Sphinx's input and output behavior.""" + +# pylint: disable=invalid-name + +import inspect import os +import re +import sys +from collections.abc import Iterator, Sequence +from datetime import datetime from subprocess import run, CalledProcessError -import inspect -import pkg_resources +from types import ModuleType +from typing import Any, Literal +from pybtex.database import Entry +from pybtex.plugin import register_plugin from pybtex.style.formatting.plain import Style as PlainStyle from pybtex.style.labels import BaseLabelStyle -from pybtex.plugin import register_plugin - - -# -- Project information ----------------------------------------------------- -project = 'corelay' -copyright = '2021, corelay' -author = 'corelay' - -# -- General configuration --------------------------------------------------- - -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.napoleon', - 'sphinx.ext.linkcode', - 'sphinx.ext.mathjax', - 'sphinx.ext.extlinks', - 'sphinx_rtd_theme', - 'sphinx_copybutton', - 'sphinxcontrib.datatemplates', - 'sphinxcontrib.bibtex', -] - +from sphinx.application import Sphinx -def config_inited_handler(app, config): - os.makedirs(os.path.join(app.srcdir, app.config.generated_path), exist_ok=True) +LanguageDomain = Literal['py', 'c', 'cpp', 'javascript'] +"""Represents the different language domains for which the linkcode extension can generate links to the source in the repository.""" -def autodoc_skip_member_handler(app, what, name, obj, skip, options): - if isinstance(obj, property): - return True - return None +class AuthorYearLabelStyle(BaseLabelStyle): + """Represents a citation label style, which uses the format "[ et al., ]".""" -def setup(app): - app.add_config_value('generated_path', '_generated', 'env') - app.connect('config-inited', config_inited_handler) - app.connect('autodoc-skip-member', autodoc_skip_member_handler) - + def format_labels(self, sorted_entries: list[Entry]) -> Iterator[str]: + """Formats the labels of all bibliography entries. -templates_path = ['_templates'] -exclude_patterns = [] + Args: + sorted_entries (list[Entry]): The sorted list of bibliography entries. -copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " -copybutton_prompt_is_regexp = True -copybutton_line_continuation_character = "\\" -copybutton_here_doc_delimiter = "EOT" + Yields: + str: Yields the formatted labels of all bibliography entries. + """ -html_theme = 'sphinx_rtd_theme' -html_favicon = '_static/favicon.svg' -html_static_path = ['_static'] - -bibtex_bibfiles = ['bibliography.bib'] -bibtex_default_style = 'author_year_style' -bibtex_reference_style = 'author_year' - - -class AuthorYearLabelStyle(BaseLabelStyle): - def format_labels(self, sorted_entries): for entry in sorted_entries: yield f'[{entry.persons["author"][0].last_names[0]} et al., {entry.fields["year"]}]' class AuthorYearStyle(PlainStyle): - default_label_style = AuthorYearLabelStyle + """Represents a custom bibliography style, which uses the citation label format "[ et al., ]".""" + default_label_style: str | type = AuthorYearLabelStyle + """The label style to use for the bibliography entries.""" -register_plugin('pybtex.style.formatting', 'author_year_style', AuthorYearStyle) +def get_latest_git_tag() -> str: + """Retrieves the latest Git tag in the source code repository. -def getrev(): + Returns: + str: Returns the name of the latest Git tag in the source code repository. If no tags are available, then "main" is returned. + """ + + # Tries to get the most recent tag in the source code repository using the git describe command, which returns the closest tag that can be reached + # from the specified revision, which in this case is the latest commit on main, if no tags are available, then "main" is returned as a fallback try: - revision = run( + return run( ['git', 'describe', '--tags', 'HEAD'], capture_output=True, check=True, text=True ).stdout[:-1] except CalledProcessError: - revision = 'main' + return 'main' - return revision +def get_object_by_name(name: str, module: ModuleType) -> ModuleType | type[Any] | None: + """Retrieves the object with the specified name from the specified module. The object can be a module, a class, a method, a function, or a + variable. If the name is not valid or the object does not exist, then None is returned. -REVISION = getrev() + Args: + name (str): The name of the object to retrieve. + module (ModuleType): The module from which to retrieve the object. -extlinks = { - 'repo': ( - f'https://github.com/virelay/corelay/blob/{REVISION}/%s', - '%s' - ) -} + Returns: + ModuleType | type[Any] | None: Returns the object with the specified name from the specified module. If the object does not exist, then + None is returned. + """ -LINKCODE_URL = ( - f'https://github.com/virelay/corelay/blob/{REVISION}' - '/src/{filepath}#L{linestart}-L{linestop}' -) + name_components = name.split('.') + if not name_components: + return None + object_to_retrieve: ModuleType | type[Any] = module + for name_component in name_components: + try: + object_to_retrieve = getattr(object_to_retrieve, name_component) + except AttributeError: + return None -# revised from https://gist.github.com/nlgranger/55ff2e7ff10c280731348a16d569cb73 -def linkcode_resolve(domain, info): - if domain != 'py' or not info['module']: - return None + return object_to_retrieve + + +def get_top_level_module(module_name_hierarchy: str) -> ModuleType | None: + """Retrieves the top-level module from the specified module name hierarchy. For example, if the module hierarchy is `a.b.c`, then the module `a` + will be returned. If the module name hierarchy is `a`, then `a` will be returned. If the module name hierarchy is not valid or the top-level + module of the hierarchy does not exist, then None is returned. - modname = info['module'] - topmodulename = modname.split('.')[0] - fullname = info['fullname'] + Args: + module_name_hierarchy (str): The module name hierarchy from which the top-level module is to be retrieved. - submod = sys.modules.get(modname) - if submod is None: + Returns: + ModuleType | None: Returns the top-level module from the specified module name. If the module does not exist, then None is returned. + """ + + module_hierarchy_names = module_name_hierarchy.split('.') + if not module_hierarchy_names: return None - obj = submod - for part in fullname.split('.'): - try: - obj = getattr(obj, part) - except Exception: - return None + top_module_name = module_hierarchy_names[0] + top_module = sys.modules.get(top_module_name) + if top_module is None: + return None + + return top_module + + +def is_type_alias_for_literal(object_to_check: Any) -> bool: + """Checks if the specified object is a type alias for a Literal[...] type. + + Args: + object_to_check (Any): The object to check. + + Returns: + bool: Returns True if the specified object is a type alias for a Literal[...] type, otherwise False. + """ + + return type(object_to_check).__name__ == '_LiteralGenericAlias' + +def get_source_code_location_of_literal_type_alias(name: str, module: ModuleType) -> tuple[str | None, int | None, int | None]: + """Retrieves the path to the source code file that contains the type alias for the Literal[...] type, as well as the start and end line numbers of + the type alias declaration. If the type alias for the Literal[...] type does not exist, then None is returned for all three values. + + Args: + name (str): The name of the type alias for the Literal[...] type. + module (ModuleType): The module that contains the type alias for the Literal[...] declaration. + + Returns: + tuple[str | None, int | None, int | None]: Returns a tuple containing the path to the source code file that contains the type alias for the + Literal[...] type, as well as the start and end line numbers of the type alias declaration. If the type alias for the Literal[...] type + does not exist, then None is returned for all three values. + """ + + # Checks if the module's file path is available, if not, then there is no source file that we can check + if module.__file__ is None: + return None, None, None + + # Loads the module's source code file into memory + with open(module.__file__, mode='r', encoding='utf-8') as module_file: + module_file_contents = module_file.read() + + # Checks if the type alias declaration is present in the module's source code file, if not then we cannot generate a URL to the source code + match: re.Match[str] | None = re.search( + r'(type \s*)?' + name + r'\s* (: \s* TypeAlias)? \s* = \s* Literal \s* \[ \s* (\s* [\'"] [^\'"]* [\'"] ,?)* \s* \]', + module_file_contents, + re.VERBOSE + ) + if not match: + return None, None, None + + # Gets the start and the end line number of the type alias declaration + start_index, end_index = match.span() + start_line_number = module_file_contents.count('\n', 0, start_index) + 1 + end_line_number = module_file_contents.count('\n', 0, end_index) + 1 + + # Returns the path to the source code file that contains the type alias declaration, and the start and end line numbers of the declaration + return module.__file__, start_line_number, end_line_number + + +def get_source_code_location_of_object(object_to_get_location_for: ModuleType | type[Any]) -> tuple[str | None, int | None, int | None]: + """Retrieves the path to the source code file that contains the specified object, as well as the start and end line numbers of the object's + definition. The object can be a module, a class, a method, a or function. If the source code file path is not available, or the object's + definition cannot be found, then None is returned for all three values. + + Args: + object_to_get_location_for (ModuleType | type[Any]): The object for which the source code location is to be retrieved. + + Returns: + tuple[str | None, int | None, int | None]: Returns a tuple containing the path to the source code file that contains the specified object, + as well as the start and end line numbers of the object's definition. If the source code file path is not available, or the object's + definition cannot be found, then None is returned for all three values. + """ + + # Gets the absolute path to the source code file that contains the object; if the object is not a module, class, method, function, traceback, + # frame, or code object, but, for example, a built-in function, a built-in class, a built-in module, or a property, then a TypeError is raised + # because built-in objects are directly implemented in C and no source code is available, and properties can presumable not be located as they + # consist of two methods that may be defined in separate code files when one of them is defined in a base class; in both cases, None is returned + # for the source file path, start line number, and end line number + try: + source_file_path = inspect.getsourcefile(object_to_get_location_for) + except TypeError: + source_file_path = None + if source_file_path is None: + return None, None, None + + # Retrieves the line number at which the object's definition starts and the line number where it ends; if the source code file cannot be + # retrieved, then an OSError is raised, if the object is not a module, class, method, function, traceback, frame, or code object, but, for + # example, a built-in function, a built-in class, a built-in module, or a property, then a TypeError is raised because built-in objects are + # directly implemented in C and no source code is available, and properties can presumable not be located as they consist of two methods that may + # not come directly one after another; in both cases, None is returned for the source file path, start line number, and end line number try: - modpath = pkg_resources.require(topmodulename)[0].location - filepath = os.path.relpath(inspect.getsourcefile(obj), modpath) - if filepath is None: - return - except Exception: + source, start_line_number = inspect.getsourcelines(object_to_get_location_for) + end_line_number = start_line_number + len(source) - 1 + return source_file_path, start_line_number, end_line_number + except (OSError, TypeError): + return None, None, None + + +def linkcode_resolve(domain: LanguageDomain, info: dict[str, str]) -> str | None: + """Determines the URL to the source code corresponding to the object in the specified domain with the provided information. This function was + adapted from https://gist.github.com/nlgranger/55ff2e7ff10c280731348a16d569cb73. + + Args: + domain (LanguageDomain): Specified the language domain the object is in. Can be one of "py", "c", "cpp", or "javascript". + info (dict[str, str]): A dictionary, which contains further information about the object for which the URL is to be retrieved. Depending on + the domain, the following keys are guaranteed to be present: + - domain is "py": + - "module", which contains the name of the module. + - "fullname", which contains the full name of the object. + - domain is "c": + - "names", which contains a list of names for the object. + - domain is "cpp": + - "names", which contains a list of names for the object. + - domain is "javascript": + - "object", which is the name of the object. + - "fullname", which is the name of the item. + + Returns: + str | None: Returns the URL of the source code corresponding to the specified object. If no URL is available, then None is returned. + """ + + # We are only interested in Python source code URLs + if domain != 'py' or 'module' not in info or 'fullname' not in info: return None - try: - source, lineno = inspect.getsourcelines(obj) - except OSError: + # Gets the module in which the object resides for which the source code URL is to be retrieved + module: ModuleType | None = sys.modules.get(info['module']) + if module is None: + return None + + # Gets the object for which the source code URL is to be retrieved + object_to_get_url_for: ModuleType | type[Any] | None = get_object_by_name(info['fullname'], module) + if object_to_get_url_for is None: return None + + # Unfortunately, type aliases for Literal[...]'s (such as LanguageDomain) are not supported by the getsourcefile function, because they are not + # real types, but objects of the typing._LiteralGenericAlias class, and getsourcefile only supports modules, classes, methods, and functions; + # therefore, the only option we have is that we find the declaration of the type alias in the file of the module it is located in and then use + # the line number of the type alias declaration to generate the URL to the source code file on GitHub; otherwise, we can use the getsourcefile and + # getsourcelines functions to generate the URL to the source code file on GitHub + if is_type_alias_for_literal(object_to_get_url_for): + object_file_path, start_line_number, end_line_number = get_source_code_location_of_literal_type_alias(info['fullname'], module) else: - linestart, linestop = lineno, lineno + len(source) - 1 + object_file_path, start_line_number, end_line_number = get_source_code_location_of_object(object_to_get_url_for) + + # If the source code file path, the start line number, or the end line number is not available, then we cannot generate a URL to the source code + if object_file_path is None or start_line_number is None or end_line_number is None: + return None + + # The path to the source code file is absolute, so it must be converted to a path relative to the top module's path (example: source code file + # path: /a/b/c/corelay/d/e.py, top module path: /a/b/c/corelay, and what we need is corelay/d/e.py); the path returned by the __file__ attribute + # of the top module points to the module's __init__.py file, e.g., /a/b/c/corelay/__init__.py, so we need to remove the __init__.py part to get + # the top module's path; then, we also need to remove the directory that the top module is contained in, e.g., /a/b/c; this is needed because the + # function that turns the absolute into a relative path removes the common prefix of the two paths, which would mean that we would end up with + # d/e.py instead of corelay/d/e.py + top_module = get_top_level_module(info['module']) + if top_module is None or top_module.__file__ is None: + return None + top_module_path = os.path.abspath(os.path.join(os.path.dirname(top_module.__file__), '..')) + object_file_path = os.path.relpath(object_file_path, top_module_path) + + # Composes the URL to the source code file on GitHub and returns it + return f'https://github.com/virelay/corelay/blob/{LATEST_GIT_TAG}/source/{object_file_path}#L{start_line_number}-L{end_line_number}' + - return LINKCODE_URL.format(filepath=filepath, linestart=linestart, linestop=linestop) +def setup(app: Sphinx) -> None: + """Is invoked by Sphinx, when this build configuration file is executed. + + Args: + app (Sphinx): The Sphinx application. + """ + + # Sets the name of the directory into which generated documentation files are to be written and makes sure that the directory exists (this is done + # by signing up for the config-inited event, which is emitted when the configuration has been fully initialized) + app.add_config_value('generated_path', '_generated', 'env') + app.connect( + 'config-inited', + lambda app, _: os.makedirs(os.path.join(app.srcdir, app.config.generated_path), exist_ok=True) + ) + + +# Sets the basic project information +project = 'CoRelAy' +project_copyright = f'{datetime.now().year}, CoRelAy' +author = 'CoRelAy Contributors' + +# Specifies the paths for the directories that contain extra templates and static files, and configures the favicon for the HTML pages +templates_path = ['_templates'] +html_static_path = ['_static'] +html_favicon = '_static/favicon.svg' + +# Specifies a list of patterns, relative to source directory, that match files and directories to ignore when looking for source files, in this case, +# nothing needs to be excluded +exclude_patterns: Sequence[str] = [] + +# Specifies the Sphinx extensions that are used by this documentation +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.extlinks', + 'sphinx.ext.linkcode', + 'sphinx.ext.mathjax', + 'sphinx.ext.napoleon', + 'sphinxcontrib.bibtex', + 'sphinxcontrib.datatemplates', + 'sphinx_copybutton', + 'sphinx_rtd_theme' +] + +# Registers the pybtex.style.formatting plugin, which is used to format citations of bibliography entries to the format +# "[ et al., ]" +register_plugin('pybtex.style.formatting', 'author_year_style', AuthorYearStyle) + +# Configures the Sphinx plugin, which adds a copy button to code blocks +copybutton_prompt_text = r'>>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: ' +copybutton_prompt_is_regexp = True +copybutton_line_continuation_character = '\\' +copybutton_here_doc_delimiter = 'EOT' + +# Configures the theme of the HTML pages, which is set to the Read the Docs theme that was installed as a plugin +html_theme = 'sphinx_rtd_theme' + +# Configures the Sphinx plugin, which allows us to insert external links to the GitHub repository using ":repo:`file-path`" instead of the full URL +LATEST_GIT_TAG = get_latest_git_tag() +extlinks = { + 'repo': ( + f'https://github.com/virelay/corelay/blob/{LATEST_GIT_TAG}/%s', + '%s' + ) +} + +# Configures the Sphinx plugin, which allows BibTeX citations to be inserted into documentation +bibtex_bibfiles = ['bibliography.bib'] +bibtex_default_style = 'author_year_style' +bibtex_reference_style = 'author_year' diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst index 18b9bbf..45d43fd 100644 --- a/docs/source/getting-started.rst +++ b/docs/source/getting-started.rst @@ -47,10 +47,10 @@ A **Processor** can be defined in the following way: .. code-block:: python - import numpy as np + from types import FunctionType + from typing import Annotated from corelay.processor.base import Processor, Param - from types import FunctionType class MyProcess(Processor): @@ -58,17 +58,18 @@ A **Processor** can be defined in the following way: # and will be set in __init__ automatically, which expects keyword # arguments with the same name the first value is a type specification, # the second a default value - stuff = Param(dtype=int, default=2) + stuff: Annotated[int, Param(dtype=int, default=2)] + # as class methods have to be bound explicitly, func here acts like a # static function of MyProcess. For more information see # corelay.processor.base.FunctionProcessor - func = Param(FunctionType, lambda x: x**2) + func: Annotated[FunctionType, Param(FunctionType, lambda x: x**2)] # Parameters can be accessed as self. - def function(self, data): + def function(self, data: Any) -> Any: return self.stuff * self.func(data) + 3 -Every **Processor** is a subclass of +Every **Processor** is a sub-class of :py:class:`~corelay.processor.base.Processor`, and must implement :py:meth:`~corelay.processor.base.Processor.function`, which typically only uses a single positional argument. @@ -106,17 +107,17 @@ Pipeline and Task # to the task changes the default value of the Processor before creation: prepreprocess = Task( proc_type=FunctionProcessor, - default=(lambda self, x: x * 2), + default=lambda self, x: x * 2, bind_method=True ) # Otherwise, we do not need to supply `self` for the default function: - preprocess = Task(proc_type=FunctionProcessor, default=(lambda x: x**2)) + preprocess = Task(proc_type=FunctionProcessor, default=lambda x: x**2) pdistance = Task(Distance, SciPyPDist(metric='sqeuclidean')) affinity = Task(Affinity, RadialBasisFunction(sigma=1.0)) # empty task, does nothing (except return input) by default postprocess = Task() -Every **Pipeline** is a subclass of :py:class:`~corelay.pipeline.base.Pipeline`. +Every **Pipeline** is a sub-class of :py:class:`~corelay.pipeline.base.Pipeline`. **Tasks** of a pipeline are created by assigning an instance of :py:class:`~corelay.pipeline.base.Task` as a class attribute, similar to **Params** in **Processors**. @@ -138,7 +139,7 @@ way: .. code-block:: python - import numpy as np + import numpy from corelay.processor.base import FunctionProcessor from corelay.processor.affinity import RadialBasisFunction @@ -146,7 +147,7 @@ way: # Use Pipeline 'as is' pipeline = MyPipeline() - output1 = pipeline(np.random.rand(5, 3)) + output1 = pipeline(numpy.random.rand(5, 3)) print('Pipeline output:', output1) # Tasks are filled with Processes during initialization of the Pipeline @@ -157,13 +158,13 @@ way: # True. Supplying a value here avoids falling back to the default # value, and thus we do not need a `self` argument for our function: prepreprocess=FunctionProcessor( - function=(lambda x: x + 1), bind_method=False + processing_function=lambda x: x + 1, bind_method=False ), - preprocess=(lambda x: x.mean(1)), + preprocess=lambda x: x.mean(1), postprocess = MyProcess(stuff=3) ) - custom_pipeline.affinity = RadialBasisFunction(sigma=.1), - output2 = custom_pipeline(np.ones((5, 3, 5))) + custom_pipeline.affinity = RadialBasisFunction(sigma=0.1), + output2 = custom_pipeline(numpy.ones((5, 3, 5))) print('Custom pipeline output:', output2) Like **Processors**, executing a **Pipeline** can be done by simply calling it @@ -180,89 +181,132 @@ similar version of the following code may be found in .. code-block:: python + """An example script, which uses memoization to store (intermediate) results.""" + import time + from collections.abc import Sequence + from typing import Annotated, Any, SupportsIndex import h5py - import numpy as np + import numpy + from numpy.typing import NDArray from corelay.base import Param - from corelay.processor.base import Processor - from corelay.processor.flow import Sequential, Parallel + from corelay.io.storage import HashedHDF5 from corelay.pipeline.spectral import SpectralClustering + from corelay.processor.base import Processor from corelay.processor.clustering import KMeans from corelay.processor.embedding import TSNEEmbedding, EigenDecomposition - from corelay.io.storage import HashedHDF5 + from corelay.processor.flow import Sequential, Parallel - # custom processors can be implemented by defining a function attribute class Flatten(Processor): - def function(self, data): - return data.reshape(data.shape[0], np.prod(data.shape[1:])) + """Represents a CoRelAy processor, which flattens its input data.""" + + def function(self, data: Any) -> Any: + """Applies the flattening to the input data. + + Args: + data (Any): The input data that is to be flattened. + + Returns: + Any: Returns the flattened data. + """ + + input_data: NDArray[Any] = data + input_data.sum() + return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) class SumChannel(Processor): - # parameters can be assigned by defining a class-owned Param instance - axis = Param(int, 1) - def function(self, data): - return data.sum(1) + """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" + + def function(self, data: Any) -> Any: + """Applies the summation over the channels to the input data. + + Args: + data (Any): The input data that is to be summed over its channels. + + Returns: + Any: Returns the data that was summed up over its channels. + """ + + input_data: NDArray[Any] = data + return input_data.sum(axis=1) class Normalize(Processor): - def function(self, data): - data = data / data.sum((1, 2), keepdims=True) - return data - - - np.random.seed(0xDEADBEEF) - fpath = 'test.analysis.h5' - with h5py.File(fpath, 'a') as fd: - # HashedHDF5 is an io-object that stores outputs of Processors based on - # hashes in hdf5 - iobj = HashedHDF5(fd.require_group('proc_data')) - - # generate some exemplary data - data = np.random.normal(size=(64, 3, 32, 32)) - n_clusters = range(2, 20) - - # SpectralClustering is an Example for a pre-defined Pipeline - pipeline = SpectralClustering( - # processors, such as EigenDecomposition, can be assigned to - # pre-defined tasks - embedding=EigenDecomposition(n_eigval=8, io=iobj), - # flow-based Processors, such as Parallel, can combine multiple - # Processors broadcast=True copies the input as many times as there - # are Processors broadcast=False instead attempts to match each - # input to a Processor - clustering=Parallel([ - Parallel([ - KMeans(n_clusters=k, io=iobj) for k in n_clusters - ], broadcast=True), - # io-objects will be used during computation when supplied to - # Processors if a corresponding output value (here identified by - # hashes) already exists, the value is not computed again but - # instead loaded from the io object - TSNEEmbedding(io=iobj) - ], broadcast=True, is_output=True) - ) - # Processors (and Params) can be updated by simply assigning - # corresponding attributes - pipeline.preprocessing = Sequential([ - SumChannel(), - Normalize(), - Flatten() - ]) - - start_time = time.perf_counter() - - # Processors flagged with "is_output=True" will be accumulated in the - # output the output will be a tree of tuples, with the same hierarchy as - # the pipeline (i.e. clusterings here contains a tuple of the k-means - # outputs) - clusterings, tsne = pipeline(data) - - # since we store our results in a hdf5 file, subsequent calls will not - # compute the values (for the same inputs), but rather load them from the - # hdf5 file try running the script multiple times - duration = time.perf_counter() - start_time - print(f'Pipeline execution time: {duration:.4f} seconds') + """Represents a CoRelAy processor, which normalizes its input data.""" + + axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] + """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" + + def function(self, data: Any) -> Any: + """Normalizes the specified input data. + + Args: + data (Any): The input data that is to be normalized. + + Returns: + Any: Returns the normalized input data. + """ + + input_data: NDArray[Any] = data + return input_data / input_data.sum(self.axes, keepdims=True) + + + def main() -> None: + """The entrypoint to the memoize_spectral_pipeline script.""" + + # Fixes the random seed for reproducibility + numpy.random.seed(0xDEADBEEF) + + # Opens an HDF5 file in append mode for the storing the results of the analysis and the memoization of intermediate pipeline results + with h5py.File('test.analysis.h5', 'a') as analysis_file: + + # Creates a HashedHDF5 IO object, which is an IO object that stores outputs of processors based on hashes in an HDF5 file + io_object = HashedHDF5(analysis_file.require_group('proc_data')) + + # Generates some exemplary data + data = numpy.random.normal(size=(64, 3, 32, 32)) + number_of_clusters = range(2, 20) + + # Creates a SpectralClustering pipeline, which is one of the pre-defined built-in pipelines + pipeline = SpectralClustering( + + # Processors, such as EigenDecomposition, can be assigned to pre-defined tasks + embedding=EigenDecomposition(n_eigval=8, io=io_object), + + # Flow-based processors, such as Parallel, can combine multiple processors; broadcast=True copies the input as many times as there are + # processors; broadcast=False instead attempts to match each input to a processor + clustering=Parallel([ + Parallel([ + KMeans(n_clusters=k, io=io_object) for k in number_of_clusters + ], broadcast=True), + + # IO objects will be used during computation when supplied to processors, if a corresponding output value (here identified by hashes) + # already exists, the value is not computed again but instead loaded from the IO object + TSNEEmbedding(io=io_object) + ], broadcast=True, is_output=True) + ) + + # Processors (and Params) can be updated by simply assigning corresponding attributes + pipeline.preprocessing = Sequential([ + SumChannel(), + Normalize(), + Flatten() + ]) + + # Processors flagged with "is_output=True" will be accumulated in the output; the output will be a tree of tuples, with the same hierarchy as + # the pipeline (i.e., _clusterings here contains a tuple of the k-means outputs) + start_time = time.perf_counter() + _clusterings, _tsne = pipeline(data) + + # Since we memoize our results in an HDF5 file, subsequent calls will not compute the values (for the same inputs), but rather load them from + # the HDF5 file; try running the script multiple times + duration = time.perf_counter() - start_time + print(f'Pipeline execution time: {duration:.4f} seconds') + + if __name__ == '__main__': + main() diff --git a/example/corelay_basics.py b/example/corelay_basics.py index 960b249..ad39e09 100644 --- a/example/corelay_basics.py +++ b/example/corelay_basics.py @@ -1,62 +1,117 @@ -import numpy as np +"""An example script, which demonstrates the usage of CoRelAy's pipeline and processor classes.""" +from types import FunctionType +from typing import Annotated, Any + +import numpy + +from corelay.base import Param from corelay.pipeline.base import Pipeline, Task -from corelay.processor.base import Processor, Param, FunctionProcessor from corelay.processor.affinity import Affinity, RadialBasisFunction +from corelay.processor.base import FunctionProcessor, Processor from corelay.processor.distance import Distance, SciPyPDist -from types import FunctionType -# Processors -class MyProcess(Processor): - # Parameters are registered by defining a class attribute of type Param, and will be set in __init__ automatically, - # which expects keyword arguments with the same name the first value is a type specification, the second a default - # value - stuff = Param(dtype=int, default=2) - # as class methods have to be bound explicitly, func here acts like a static function of MyProcess to bind it, see - # :obj:`corelay.processor.base.FunctionProcessor` - func = Param(FunctionType, lambda x: x**2) +class MyProcessor(Processor): + """A custom CoRelAy processor, which applies a configurable function to its input data and multiplies it by a configurable value.""" + + multiplier: Annotated[int, Param(dtype=int, default=2)] + """An integer parameter, which is multiplied with the result of the function. + + Note: + Parameters are registered by defining class attributes of type ``Param``. These will be automatically realized as instance attributes at + runtime and can be initialized in ``__init__`` using positional and keyword arguments of the same name, depending on the ``is_positional`` + parameter of the ``Param``. The first value is a type specification, the second a default value. + """ - # Parameters can be accessed as self. - def function(self, data): - return self.stuff * self.func(data) + 3 + function_to_apply: Annotated[FunctionType, Param(FunctionType, lambda x: x**2)] + """A function, which is applied to the input data. + + Note: + As class methods have to be bound explicitly, ``function_to_apply`` here acts like a static function of ``MyProcessor``. If you want to have + processor that applies a custom function and that is bound to the class and has access to self, please refer to + :obj:`corelay.processor.base.FunctionProcessor`. + """ + + def function(self, data: Any) -> Any: + """Applies the custom function ``function_to_apply`` to the input data and multiplies it by the parameter ``multiplier``. + + Args: + data (Any): The input data that is to be processed. + + Returns: + Any: Returns the processed data. + """ + + # Parameters can be accessed as self. + return self.multiplier * self.function_to_apply(data) + 3 -# Pipelines class MyPipeline(Pipeline): - # Task are registered in order by creating a class attribute of type Task() and, like params, are expected to be - # supplied with the same name in __init__ as a keyword argument. The first value is an optional expected Process - # type, second is a default value, which has to be an instance of that type. If the default argument is not a - # Process, it will be converted to a FunctionProcessor by default, functions fed to FunctionProcessors are by - # default not bound to the class. To bind them, we can supply `bind_method=True` to the FunctionProcessor. Supplying - # it to the task changes the default value of the Processor before creation: - prepreprocess = Task(proc_type=FunctionProcessor, default=(lambda self, x: x * 2), bind_method=True) - # Otherwise, we do not need to supply `self` for the default function: - preprocess = Task(proc_type=FunctionProcessor, default=(lambda x: x**2)) - pdistance = Task(Distance, SciPyPDist(metric='sqeuclidean')) + """A custom CoRelAy pipeline, which applies a series of processors to its input data.""" + + pre_pre_process = Task(proc_type=FunctionProcessor, default=lambda self, x: x * 2, bind_method=True) + """A pre-pre-processing task, which applies a function to the input data. By default, the input data is multiplied by 2. The ``FunctionProcessor`` + class is a ``Processor`` that applies a function to the input data. + + Note: + Tasks are registered by creating a class attribute of type ``Task`` and, like parameters, are expected to be supplied with the same name in + ``__init__`` as a keyword argument. The first value is an optional type that determines which type of ``Process`` is expected, second is a + default value, which has to be an instance of that type. If the default argument is not a ``Process``, it will be converted to a + ``FunctionProcessor``. By default, functions fed to ``FunctionProcessors`` are by not bound to the class. To bind them, we can supply + `bind_method=True` to the ``FunctionProcessor``. Supplying it to the task changes the default value of the ``Processor`` before creation. + """ + + pre_process = Task(proc_type=FunctionProcessor, default=lambda x: x**2) + """A pre-processing task, which applies a function to the input data. By default, the input data is squared. + + Note: + The ``bind_method`` parameter is omitted here and therefore defaults to False. This means that the function is not bound to the class and does + not have access to ``self``. + """ + + pairwise_distance = Task(Distance, SciPyPDist(metric='sqeuclidean')) + """A task, which applies a pairwise distance function to the input data. By default, the squared euclidean distance is used. The ``Distance`` + class is a base class for all distance processors. + """ + affinity = Task(Affinity, RadialBasisFunction(sigma=1.0)) - # empty task, does nothing (except return input) by default - postprocess = Task() + """A task, which applies an affinity function to the input data. By default, the radial basis function is used. The ``Affinity`` class is a base + class for all affinity processors. + """ + + post_process = Task() + """A post-processing task, which does nothing by default and returns the input data as is.""" -def main(): - # Use Pipeline 'as is' +def main() -> None: + """The entrypoint to the corelay_basics script.""" + + # Creates a new pipeline without specifying any parameters, which means that the default values of the tasks will be used pipeline = MyPipeline() - output1 = pipeline(np.random.rand(5, 3)) - print('Pipeline output:', output1) + first_output = pipeline(numpy.random.rand(5, 3)) + print('Pipeline output:', first_output) - # Tasks are filled with Processes during initialization of the Pipeline class - # keyword arguments do not have to be in order, and if not supplied, the default value will be used + # Tasks are filled with processors during initialization of the Pipeline class; keyword arguments do not have to be in order, and if not supplied, + # the default value will be used custom_pipeline = MyPipeline( - # The pipeline's Task sets the `bind_method` Parameter's default to True. Supplying a value here avoids falling - # back to the default value, and thus we do not need a `self` argument for our function: - prepreprocess=FunctionProcessor(function=(lambda x: x + 1), bind_method=False), - preprocess=(lambda x: x.mean(1)), - affinity=RadialBasisFunction(sigma=.1), - postprocess=MyProcess(stuff=3) + + # By setting the ``bind_method`` parameter to ``False``, the function is not bound to the class and we do not need to a ``self`` argument + pre_pre_process=FunctionProcessor(processing_function=lambda x: x + 1, bind_method=False), + + # The ``pre_process`` task is set to a custom function, which is not of type ``Distance`` and is therefore automatically converted to a + # ``FunctionProcessor`` + pre_process=lambda x: x.mean(1), + + # The ``pairwise_distance`` task is omitted and therefore defaults to the squared euclidean distance; the ``affinity`` task is set to a + # ``RadialBasisFunction`` with a lower sigma value + affinity=RadialBasisFunction(sigma=0.1), + + # The empty ``post_process`` task is set to an instance of our custom processor ``MyProcessor`` and the ``multiplier`` parameter is set to 3 + post_process=MyProcessor(multiplier=3) ) - output2 = custom_pipeline(np.ones((5, 3, 5))) - print('Custom pipeline output:', output2) + second_output = custom_pipeline(numpy.ones((5, 3, 5))) + print('Custom pipeline output:', second_output) if __name__ == '__main__': diff --git a/example/hdf5_structure.py b/example/hdf5_structure.py index e414686..6527b37 100644 --- a/example/hdf5_structure.py +++ b/example/hdf5_structure.py @@ -1,179 +1,239 @@ +"""Contains functions, which generate example dataset, attribution, and analysis HDF5 files. These functions are mainly for documentation purposes to +show the structure of the different HDF5 files that can be generated using CoRelAy for the usage in ViRelAy projects. +""" + import h5py -import numpy as np +import numpy + +def make_group_example() -> None: + """Generates example dataset, attribution, and analysis HDF5 files were the dataset samples and the attribution data are stored in HDF5 groups + instead of HDF5 datasets. This is mainly used when the samples (and therefore the corresponding attribution data) do not all have the same shape + and therefore cannot be stored in a single dataset. Instead they are stored in a group and the mapping between the group keys and the sample + indices are stored in a separate group called "index". + """ -def make_group_example(): - # input file with groups with different sizes - with h5py.File('grouped.input.h5', 'w') as fd: - # input data group + # Input file with groups with different sizes + with h5py.File('grouped.input.h5', 'w') as dataset_file: + + # Input data group keys = ('a', 'b', 'c', 'd', 'e') shapes = (4, 5, 6, 5, 4) channels = 3 - g_input = fd.require_group('data') + samples_group = dataset_file.require_group('data') for key, shape in zip(keys, shapes): - # each sample is its own dataset - g_input[key] = np.random.normal(size=(channels, shape, shape)).astype(np.float32) - # true label group + # Each sample is its own HDF5 dataset + samples_group[key] = numpy.random.normal(size=(channels, shape, shape)).astype(numpy.float32) + + # True label group labels = (0, 1, 0, 0, 1) - g_label = fd.require_group('label') + labels_group = dataset_file.require_group('label') for key, label in zip(keys, labels): - # here each sample is just a single number - # alternatively, we could use a 1d-array of type bool for multi-label data - g_label[key] = np.uint8(label) - # we supply a custom ordering of our samples + # Here each sample is just a single number, alternatively, we could use a 1-dimensional-array of type bool for multi-label data + labels_group[key] = numpy.uint8(label) + + # We supply a custom ordering of our samples indices = (0, 2, 1, 3, 4) - g_index = fd.require_group('index') + indices_group = dataset_file.require_group('index') for key, index in zip(keys, indices): - # each sample has only one index - g_index[key] = np.uint32(index) - - # attribution file with groups with different sizes - with h5py.File('grouped-attr_method-2.attribution.h5', 'w') as fd: - # we use attribute only subset of our data - a_indices = (2, 3, 4) - fd['index'] = np.array(a_indices, dtype=np.uint32) - - # attribution keys of our used subset - a_keys = keys[2:] - a_shapes = shapes[2:] - g_attribution = fd.require_group('data') - for key, shape in zip(a_keys, a_shapes): - g_attribution[key] = np.random.normal(size=(channels, shape, shape)).astype(np.float32) - - # attribution labels are the assigned attribution in the output layer - g_label = fd.require_group('label') - a_labels = np.array([[0, 1], [0, 1], [0, 1]]) - for key, label in zip(a_keys, a_labels): - # the output attributions can be any real number, and have the same shape as the output - g_label[key] = a_labels.astype(np.float32) - - # predictions are the model output logits - a_predictions = np.array([[0, 1], [.5, .5], [1, 0]]) - g_prediction = fd.require_group('prediction') - for key, pred in zip(a_keys, a_predictions): - g_prediction[key] = a_predictions.astype(np.float32) - - with h5py.File('grouped-attr_method-ana_topic.analysis.h5', 'w') as fd: - # we call this analysis 'My First Analysis' - fd['/my_first_analysis/name'] = 'My First Analysis' - # the used indices of the analysis, here we use all 3 in the attribution file - fd['/my_first_analysis/index'] = np.array(a_indices, dtype=np.uint32) - - # for shorter references - g_emb = fd.require_group('/my_first_analysis/embedding') - g_clu = fd.require_group('/my_first_analysis/clustering') - - n_eigvals = 2 - # verbose name of the spectral embedding - g_emb['/spectral/name'] = 'Spectral Embedding' - # spectral embedding (eigenvalue decomposition) with key 'spectral', 2 eigenvalues, here just random data - g_emb['/spectral/root'] = np.random.normal(size=(len(a_indices), n_eigvals)).astype(np.float32) - # the corresponding eigenvalues, specific to spectral embedding - g_emb['/spectral/eigenvalue'] = np.random.normal(size=n_eigvals).astype(np.float32) - - # verbose name of T-SNE - g_emb['/tsne/name'] = 'T-SNE' + + # Each sample has only one index + indices_group[key] = numpy.uint32(index) + + # Attribution file with groups with different sizes + with h5py.File('grouped-attr_method-2.attribution.h5', 'w') as attributions_file: + + # We use attribute only subset of our data + attribution_indices = (2, 3, 4) + attributions_file['index'] = numpy.array(attribution_indices, dtype=numpy.uint32) + + # Attribution keys of our used subset + attribution_keys = keys[2:] + attribution_shapes = shapes[2:] + attributions_group = attributions_file.require_group('data') + for key, shape in zip(attribution_keys, attribution_shapes): + attributions_group[key] = numpy.random.normal(size=(channels, shape, shape)).astype(numpy.float32) + + # Attribution labels are the assigned attribution in the output layer + labels_group = attributions_file.require_group('label') + attribution_labels = numpy.array([[0, 1], [0, 1], [0, 1]]) + for key, label in zip(attribution_keys, attribution_labels): + + # The output attributions can be any real number, and have the same shape as the output + labels_group[key] = attribution_labels.astype(numpy.float32) + + # Predictions are the model output logits + attribution_predictions = numpy.array([[0, 1], [0.5, 0.5], [1, 0]]) + predictions_group = attributions_file.require_group('prediction') + for key, prediction in zip(attribution_keys, attribution_predictions): + predictions_group[key] = prediction.astype(numpy.float32) + + # Analysis file with groups for embeddings and clusterings + with h5py.File('grouped-attr_method-ana_topic.analysis.h5', 'w') as analysis_file: + + # We call this analysis 'My First Analysis' + analysis_file['/my_first_analysis/name'] = 'My First Analysis' + + # The used indices of the analysis, here we use all 3 in the attribution file + analysis_file['/my_first_analysis/index'] = numpy.array(attribution_indices, dtype=numpy.uint32) + + # For shorter references + embeddings_group = analysis_file.require_group('/my_first_analysis/embedding') + clusterings_group = analysis_file.require_group('/my_first_analysis/clustering') + + # Verbose name of the spectral embedding + embeddings_group['/spectral/name'] = 'Spectral Embedding' + + # Spectral embedding (eigenvalue decomposition) with key 'spectral', 2 eigenvalues, here just random data + number_of_eigenvalues = 2 + embeddings_group['/spectral/root'] = numpy.random.normal( + size=(len(attribution_indices), number_of_eigenvalues) + ).astype(numpy.float32) + + # The corresponding eigenvalues, specific to spectral embedding + embeddings_group['/spectral/eigenvalue'] = numpy.random.normal(size=number_of_eigenvalues).astype(numpy.float32) + + # Verbose name of T-SNE + embeddings_group['/tsne/name'] = 'T-SNE' + # T-SNE embedding payload - g_emb['/tsne/root'] = np.random.normal(size=(len(a_indices), 2)).astype(np.float32) - # this T-SNE embedding is based on the spectral embedding - g_emb['/tsne/base'] = g_emb['/spectral'] - # both feature dimensions of the eigenvectors are used, but for demonstration purpose, we give the regionref - g_emb['/tsne/region'] = g_emb['/spectral/root'].regionref[:, [0, 1]] - - # we call our random clustering 'my_clustering' - g_clu['/my_clustering/name'] = 'My Random Clustering' - # clustering labels - g_clu['/my_clustering/root'] = np.random.randint(0, 2, size=len(a_indices)) - # we specify this clustering to be based on 'spectral' - g_clu['/my_clustering/base'] = g_emb['/spectral'] - # we use both feature dimensions for the spectral clustering - g_clu['/my_clustering/region'] = g_emb['/spectral/root'].regionref[:, [0, 1]] - # we chose 2 clusters - g_clu['/my_clustering/#clusters'] = 2 - - # we define a prototype for our clustering - g_clu['/my_clustering/prototype/average/name'] = 'My Random Prototype' - # for demonstration purposes, we use random data here. the first dimension is the number of clusters - g_clu['/my_clustering/prototype/average/root'] = np.random.normal(size=(2, 32, 32)).astype(np.float32) - - -def make_dataset_example(): - # input file with datasets with identical sizes - nsamples = 5 + embeddings_group['/tsne/root'] = numpy.random.normal(size=(len(attribution_indices), 2)).astype(numpy.float32) + + # This T-SNE embedding is based on the spectral embedding + embeddings_group['/tsne/base'] = embeddings_group['/spectral'] + + # Both feature dimensions of the eigenvectors are used, but for demonstration purpose, we give the regionref + embeddings_group['/tsne/region'] = embeddings_group['/spectral/root'].regionref[:, [0, 1]] + + # We call our random clustering 'my_clustering' + clusterings_group['/my_clustering/name'] = 'My Random Clustering' + + # Clustering labels + clusterings_group['/my_clustering/root'] = numpy.random.randint(0, 2, size=len(attribution_indices)) + + # We specify this clustering to be based on 'spectral' + clusterings_group['/my_clustering/base'] = embeddings_group['/spectral'] + + # We use both feature dimensions for the spectral clustering + clusterings_group['/my_clustering/region'] = embeddings_group['/spectral/root'].regionref[:, [0, 1]] + + # We chose 2 clusters + clusterings_group['/my_clustering/#clusters'] = 2 + + # We define a prototype for our clustering + clusterings_group['/my_clustering/prototype/average/name'] = 'My Random Prototype' + + # For demonstration purposes, we use random data here. the first dimension is the number of clusters + clusterings_group['/my_clustering/prototype/average/root'] = numpy.random.normal(size=(2, 32, 32)).astype(numpy.float32) + + +def make_dataset_example() -> None: + """Generates example dataset, attribution, and analysis HDF5 files were the dataset samples and the attribution data are stored in HDF5 datasets + instead of HDF5 groups. This is mainly used when the samples (and therefore the corresponding attribution data) all have the same shape and + therefore can be stored in a single dataset. Instead of having a separate "index" group which maps the keys to the indices of the samples and + attribution data, the HDF5 datasets can be directly indexed. + """ + + # Input file with datasets with identical sizes + number_of_samples = 5 shape = 7 channels = 3 labels = (0, 1, 0) - with h5py.File('dataset.input.h5', 'w') as fd: - # data samples have no identifier here and have implicit indices - fd['data'] = np.random.normal(size=(nsamples, channels, shape, shape)) - fd['label'] = np.array(labels).astype(np.uint8) - - # attribution file with datasets - with h5py.File('dataset-attr_method-2.attribution.h5', 'w') as fd: - # we use attribute only subset of our data - a_indices = (2, 3, 4) - fd['index'] = np.array(a_indices, dtype=np.uint32) - - # attribution we only use a subset of our data - fd['attribution'] = np.random.normal(size=(len(a_indices), channels, shape, shape)).astype(np.float32) - - # attribution labels are the assigned attribution in the output layer - a_labels = np.array([[0, 1], [0, 1], [0, 1]]) - # the output attributions can be any real number, and have the same shape as the output - fd['label'] = a_labels.astype(np.float32) - - # predictions are the model output logits - a_predictions = np.array([[0, 1], [.5, .5], [1, 0]]) - fd['prediction'] = a_predictions.astype(np.float32) - - # using datasets in the input/attribution does not change the analysis file structure - with h5py.File('dataset-attr_method-ana_topic.analysis.h5', 'w') as fd: - # we call this analysis 'My First Analysis' - fd['/my_first_analysis/name'] = 'My First Analysis' - # the used indices of the analysis, here we use all 3 in the attribution file - fd['/my_first_analysis/index'] = np.array(a_indices, dtype=np.uint32) - - # for shorter references - g_emb = fd.require_group('/my_first_analysis/embedding') - g_clu = fd.require_group('/my_first_analysis/clustering') - - n_eigvals = 2 - # verbose name of the spectral embedding - g_emb['/spectral/name'] = 'Spectral Embedding' - # spectral embedding (eigenvalue decomposition) with key 'spectral', 2 eigenvalues, here just random data - g_emb['/spectral/root'] = np.random.normal(size=(len(a_indices), n_eigvals)).astype(np.float32) - # the corresponding eigenvalues, specific to spectral embedding - g_emb['/spectral/eigenvalue'] = np.random.normal(size=n_eigvals).astype(np.float32) - - # verbose name of T-SNE - g_emb['/tsne/name'] = 'T-SNE' + with h5py.File('dataset.input.h5', 'w') as dataset_file: + + # Data samples have no identifier here and have implicit indices + dataset_file['data'] = numpy.random.normal(size=(number_of_samples, channels, shape, shape)) + dataset_file['label'] = numpy.array(labels).astype(numpy.uint8) + + # Attribution file with datasets + with h5py.File('dataset-attr_method-2.attribution.h5', 'w') as attributions_file: + + # We use attribute only subset of our data + attribution_indices = (2, 3, 4) + attributions_file['index'] = numpy.array(attribution_indices, dtype=numpy.uint32) + + # Attribution we only use a subset of our data + attributions_file['attribution'] = numpy.random.normal( + size=(len(attribution_indices), channels, shape, shape) + ).astype(numpy.float32) + + # Attribution labels are the assigned attribution in the output layer + attribution_labels = numpy.array([[0, 1], [0, 1], [0, 1]]) + + # The output attributions can be any real number, and have the same shape as the output + attributions_file['label'] = attribution_labels.astype(numpy.float32) + + # Predictions are the model output logits + attribution_predictions = numpy.array([[0, 1], [0.5, 0.5], [1, 0]]) + attributions_file['prediction'] = attribution_predictions.astype(numpy.float32) + + # Using datasets in the input/attribution does not change the analysis file structure + with h5py.File('dataset-attr_method-ana_topic.analysis.h5', 'w') as analysis_file: + + # We call this analysis 'My First Analysis' + analysis_file['/my_first_analysis/name'] = 'My First Analysis' + + # The used indices of the analysis, here we use all 3 in the attribution file + analysis_file['/my_first_analysis/index'] = numpy.array(attribution_indices, dtype=numpy.uint32) + + # For shorter references + embeddings_group = analysis_file.require_group('/my_first_analysis/embedding') + clusterings_group = analysis_file.require_group('/my_first_analysis/clustering') + + # Verbose name of the spectral embedding + embeddings_group['/spectral/name'] = 'Spectral Embedding' + + # Spectral embedding (eigenvalue decomposition) with key 'spectral', 2 eigenvalues, here just random data + number_of_eigenvalues = 2 + embeddings_group['/spectral/root'] = numpy.random.normal( + size=(len(attribution_indices), number_of_eigenvalues) + ).astype(numpy.float32) + + # The corresponding eigenvalues, specific to spectral embedding + embeddings_group['/spectral/eigenvalue'] = numpy.random.normal(size=number_of_eigenvalues).astype(numpy.float32) + + # Verbose name of T-SNE + embeddings_group['/tsne/name'] = 'T-SNE' + # T-SNE embedding payload - g_emb['/tsne/root'] = np.random.normal(size=(len(a_indices), 2)).astype(np.float32) - # this T-SNE embedding is based on the spectral embedding - g_emb['/tsne/base'] = g_emb['/spectral'] - # both feature dimensions of the eigenvectors are used, but for demonstration purpose, we give the regionref - g_emb['/tsne/region'] = g_emb['/spectral/root'].regionref[:, [0, 1]] - - # we call our random clustering 'my_clustering' - g_clu['/my_clustering/name'] = 'My Random Clustering' - # clustering labels - g_clu['/my_clustering/root'] = np.random.randint(0, 2, size=len(a_indices)) - # we specify this clustering to be based on 'spectral' - g_clu['/my_clustering/base'] = g_emb['/spectral'] - # we use both feature dimensions for the spectral clustering - g_clu['/my_clustering/region'] = g_emb['/spectral/root'].regionref[:, [0, 1]] - # we chose 2 clusters - g_clu['/my_clustering/#clusters'] = 2 - - # we define a prototype for our clustering - g_clu['/my_clustering/prototype/average/name'] = 'My Random Prototype' - # for demonstration purposes, we use random data here. the first dimension is the number of clusters - g_clu['/my_clustering/prototype/average/root'] = np.random.normal(size=(2, 32, 32)).astype(np.float32) - - -def main(): + embeddings_group['/tsne/root'] = numpy.random.normal(size=(len(attribution_indices), 2)).astype(numpy.float32) + + # This T-SNE embedding is based on the spectral embedding + embeddings_group['/tsne/base'] = embeddings_group['/spectral'] + + # Both feature dimensions of the eigenvectors are used, but for demonstration purpose, we give the regionref + embeddings_group['/tsne/region'] = embeddings_group['/spectral/root'].regionref[:, [0, 1]] + + # We call our random clustering 'my_clustering' + clusterings_group['/my_clustering/name'] = 'My Random Clustering' + + # Clustering labels + clusterings_group['/my_clustering/root'] = numpy.random.randint(0, 2, size=len(attribution_indices)) + + # We specify this clustering to be based on 'spectral' + clusterings_group['/my_clustering/base'] = embeddings_group['/spectral'] + + # We use both feature dimensions for the spectral clustering + clusterings_group['/my_clustering/region'] = embeddings_group['/spectral/root'].regionref[:, [0, 1]] + + # We chose 2 clusters + clusterings_group['/my_clustering/#clusters'] = 2 + + # We define a prototype for our clustering + clusterings_group['/my_clustering/prototype/average/name'] = 'My Random Prototype' + + # For demonstration purposes, we use random data here. the first dimension is the number of clusters + clusterings_group['/my_clustering/prototype/average/root'] = numpy.random.normal(size=(2, 32, 32)).astype(numpy.float32) + + +def main() -> None: + """The entrypoint to the hdf5_structure script, which generates two sets of sample HDF5 databases, one were the dataset samples and their + corresponding attributions are stored in HDF5 groups, and one were the dataset samples and their corresponding attributions are stored in HDF5 + datasets. + """ + make_group_example() make_dataset_example() diff --git a/example/memoize_spectral_pipeline.py b/example/memoize_spectral_pipeline.py index cb1ac20..4bed4a7 100644 --- a/example/memoize_spectral_pipeline.py +++ b/example/memoize_spectral_pipeline.py @@ -1,66 +1,114 @@ -'''Example using memoization to store (intermediate) results.''' +"""An example script, which uses memoization to store (intermediate) results.""" + +# pylint: disable=duplicate-code + import time +from collections.abc import Sequence +from typing import Annotated, Any, SupportsIndex import h5py -import numpy as np +import numpy +from numpy.typing import NDArray from corelay.base import Param -from corelay.processor.base import Processor -from corelay.processor.flow import Sequential, Parallel +from corelay.io.storage import HashedHDF5 from corelay.pipeline.spectral import SpectralClustering +from corelay.processor.base import Processor from corelay.processor.clustering import KMeans from corelay.processor.embedding import TSNEEmbedding, EigenDecomposition -from corelay.io.storage import HashedHDF5 +from corelay.processor.flow import Sequential, Parallel -# custom processors can be implemented by defining a function attribute class Flatten(Processor): - def function(self, data): - return data.reshape(data.shape[0], np.prod(data.shape[1:])) + """Represents a CoRelAy processor, which flattens its input data.""" + + def function(self, data: Any) -> Any: + """Applies the flattening to the input data. + + Args: + data (Any): The input data that is to be flattened. + + Returns: + Any: Returns the flattened data. + """ + + input_data: NDArray[Any] = data + input_data.sum() + return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) class SumChannel(Processor): - # parameters can be assigned by defining a class-owned Param instance - axis = Param(int, 1) + """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" + + def function(self, data: Any) -> Any: + """Applies the summation over the channels to the input data. + + Args: + data (Any): The input data that is to be summed over its channels. - def function(self, data): - return data.sum(1) + Returns: + Any: Returns the data that was summed up over its channels. + """ + + input_data: NDArray[Any] = data + return input_data.sum(axis=1) class Normalize(Processor): - def function(self, data): - data = data / data.sum((1, 2), keepdims=True) - return data + """Represents a CoRelAy processor, which normalizes its input data.""" + + axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] + """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" + + def function(self, data: Any) -> Any: + """Normalizes the specified input data. + + Args: + data (Any): The input data that is to be normalized. + + Returns: + Any: Returns the normalized input data. + """ + + input_data: NDArray[Any] = data + return input_data / input_data.sum(self.axes, keepdims=True) + +def main() -> None: + """The entrypoint to the memoize_spectral_pipeline script.""" -def main(): - np.random.seed(0xDEADBEEF) - fpath = 'test.analysis.h5' - with h5py.File(fpath, 'a') as fd: - # HashedHDF5 is an io-object that stores outputs of Processors based on hashes in hdf5 - iobj = HashedHDF5(fd.require_group('proc_data')) + # Fixes the random seed for reproducibility + numpy.random.seed(0xDEADBEEF) - # generate some exemplary data - data = np.random.normal(size=(64, 3, 32, 32)) - n_clusters = range(2, 20) + # Opens an HDF5 file in append mode for the storing the results of the analysis and the memoization of intermediate pipeline results + with h5py.File('test.analysis.h5', 'a') as analysis_file: - # SpectralClustering is an Example for a pre-defined Pipeline + # Creates a HashedHDF5 IO object, which is an IO object that stores outputs of processors based on hashes in an HDF5 file + io_object = HashedHDF5(analysis_file.require_group('proc_data')) + + # Generates some exemplary data + data = numpy.random.normal(size=(64, 3, 32, 32)) + number_of_clusters = range(2, 20) + + # Creates a SpectralClustering pipeline, which is one of the pre-defined built-in pipelines pipeline = SpectralClustering( - # processors, such as EigenDecomposition, can be assigned to pre-defined tasks - embedding=EigenDecomposition(n_eigval=8, io=iobj), - # flow-based Processors, such as Parallel, can combine multiple Processors - # broadcast=True copies the input as many times as there are Processors - # broadcast=False instead attempts to match each input to a Processor + + # Processors, such as EigenDecomposition, can be assigned to pre-defined tasks + embedding=EigenDecomposition(n_eigval=8, io=io_object), + + # Flow-based processors, such as Parallel, can combine multiple processors; broadcast=True copies the input as many times as there are + # processors; broadcast=False instead attempts to match each input to a processor clustering=Parallel([ Parallel([ - KMeans(n_clusters=k, io=iobj) for k in n_clusters + KMeans(n_clusters=k, io=io_object) for k in number_of_clusters ], broadcast=True), - # io-objects will be used during computation when supplied to Processors - # if a corresponding output value (here identified by hashes) already exists, - # the value is not computed again but instead loaded from the io object - TSNEEmbedding(io=iobj) + + # IO objects will be used during computation when supplied to processors, if a corresponding output value (here identified by hashes) + # already exists, the value is not computed again but instead loaded from the IO object + TSNEEmbedding(io=io_object) ], broadcast=True, is_output=True) ) + # Processors (and Params) can be updated by simply assigning corresponding attributes pipeline.preprocessing = Sequential([ SumChannel(), @@ -68,16 +116,13 @@ def main(): Flatten() ]) + # Processors flagged with "is_output=True" will be accumulated in the output; the output will be a tree of tuples, with the same hierarchy as + # the pipeline (i.e., _clusterings here contains a tuple of the k-means outputs) start_time = time.perf_counter() + _clusterings, _tsne = pipeline(data) - # Processors flagged with "is_output=True" will be accumulated in the output - # the output will be a tree of tuples, with the same hierarchy as the pipeline - # (i.e. clusterings here contains a tuple of the k-means outputs) - clusterings, tsne = pipeline(data) - - # since we memoize our results in a hdf5 file, subsequent calls will not compute - # the values (for the same inputs), but rather load them from the hdf5 file - # try running the script multiple times + # Since we memoize our results in an HDF5 file, subsequent calls will not compute the values (for the same inputs), but rather load them from + # the HDF5 file; try running the script multiple times duration = time.perf_counter() - start_time print(f'Pipeline execution time: {duration:.4f} seconds') diff --git a/example/virelay_analysis.py b/example/virelay_analysis.py index c6cb42b..dfb616d 100644 --- a/example/virelay_analysis.py +++ b/example/virelay_analysis.py @@ -1,78 +1,185 @@ +"""Performs a meta-analysis on an attribution database and writes them into an analysis database, from which a ViRelAy project can be created.""" + +# pylint: disable=duplicate-code + import json +import argparse +from collections.abc import Sequence +from typing import Annotated, Any, SupportsIndex import h5py -import click -import numpy as np -from skimage.metrics import structural_similarity -from scipy.stats import pearsonr +import numpy +from numpy.typing import NDArray from scipy.spatial.distance import pdist, squareform +from scipy.stats import pearsonr +from skimage.metrics import structural_similarity # pylint: disable=no-name-in-module from corelay.base import Param -from corelay.processor.base import Processor -from corelay.processor.flow import Sequential, Parallel -from corelay.processor.distance import SciPyPDist from corelay.pipeline.spectral import SpectralClustering +from corelay.processor.affinity import SparseKNN +from corelay.processor.base import Processor from corelay.processor.clustering import KMeans, DBSCAN, HDBSCAN, AgglomerativeClustering +from corelay.processor.distance import SciPyPDist from corelay.processor.embedding import TSNEEmbedding, UMAPEmbedding, EigenDecomposition -from corelay.processor.affinity import SparseKNN +from corelay.processor.flow import Sequential, Parallel class Flatten(Processor): - def function(self, data): - return data.reshape(data.shape[0], np.prod(data.shape[1:])) + """Represents a CoRelAy processor, which flattens its input data.""" + + def function(self, data: Any) -> Any: + """Applies the flattening to the input data. + + Args: + data (Any): The input data that is to be flattened. + + Returns: + Any: Returns the flattened data. + """ + input_data: NDArray[Any] = data + return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) class SumChannel(Processor): - def function(self, data): - return data.sum(1) + """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" + + def function(self, data: Any) -> Any: + """Applies the summation over the channels to the input data. + + Args: + data (Any): The input data that is to be summed over its channels. + + Returns: + Any: Returns the data that was summed up over its channels. + """ + + input_data: NDArray[Any] = data + return input_data.sum(axis=1) class Absolute(Processor): - def function(self, data): - return np.absolute(data) + """Represents a CoRelAy processor, which computes the absolute value of its input data.""" + + def function(self, data: Any) -> Any: + """Computes the absolute value of the specified input data. + + Args: + data (Any): The input data for which the absolute value is to be computed. + + Returns: + Any: Returns the absolute value of the input data. + """ + + input_data: NDArray[Any] = data + return numpy.absolute(input_data) class Normalize(Processor): - axes = Param(tuple, (1, 2)) + """Represents a CoRelAy processor, which normalizes its input data.""" + + axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] + """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" - def function(self, data): - data = data / data.sum(self.axes, keepdims=True) - return data + def function(self, data: Any) -> Any: + """Normalizes the specified input data. + Args: + data (Any): The input data that is to be normalized. -def csints(string): - return tuple(int(elem) for elem in string.split(',')) + Returns: + Any: Returns the normalized input data. + """ + + input_data: NDArray[Any] = data + return input_data / input_data.sum(self.axes, keepdims=True) class Histogram(Processor): - bins = Param(int, 256) + """Represents a CoRelAy processor, which computes a histogram over its input data.""" + + bins: Annotated[int, Param(int, 256)] + """A parameter of the processor, which determines the number of bins that are used to compute the histogram.""" + + def function(self, data: Any) -> Any: + """Computes histograms over the specified input data. One histogram is computed for each channel and each sample in a batch of input data. + + Args: + data (Any): The input data over which the histograms are to be computed. - def function(self, data): - hists = np.stack([ - np.stack([ - np.histogram( - arr.reshape(arr.shape[0], np.prod(arr.shape[1:3])), + Returns: + Any: Returns the histograms that were computed over the input data. + """ + + input_data: NDArray[Any] = data + return numpy.stack([ + numpy.stack([ + numpy.histogram( + sample.reshape(sample.shape[0], numpy.prod(sample.shape[1:3])), bins=self.bins, density=True - ) for arr in channel - ]) for channel in data.transpose(3, 0, 1, 2)]) - return hists + ) for sample in channel + ]) for channel in input_data.transpose(3, 0, 1, 2)]) + + +class SSIM(Processor): + """Represents a CoRelAy processor, which computes the structural similarity index (SSIM) of the data.""" + + def function(self, data: Any) -> Any: + """Computes the SSIM of the specified input data. + + Args: + data (Any): The input data for which the SSIM is to be computed. Each channel of the input data is treated as a separate sample and the + SSIM is computed between each pair of samples. The input data is expected to have the shape `(number_of_samples, height, width)`. + + Returns: + Any: Returns a square distance matrix, where each element i, j contains the SSIM between the samples i and j. + """ + + input_data: NDArray[Any] = data + number_of_samples, height, width = input_data.shape + distance_matrix: NDArray[Any] = pdist( + input_data.reshape(number_of_samples, height * width), + metric=lambda x, y: structural_similarity(x.reshape(height, width), y.reshape(height, width)) # type: ignore[no-untyped-call] + ) + return squareform(distance_matrix) class PCC(Processor): - def function(self, data): - return squareform(pdist(data, metric=lambda x, y: pearsonr(x, y)[0])) + """Represents a CoRelAy processor, which computes the Pearson correlation coefficient (PCC) of the data.""" + def function(self, data: Any) -> Any: + """Computes the PCC of the specified input data. -class SSIM(Processor): - def function(self, data): - N, H, W = data.shape - return squareform(pdist( - data.reshape(N, H * W), - metric=lambda x, y: structural_similarity(x.reshape(H, W), y.reshape(H, W)) - )) + Args: + data (Any): The input data for which the PCC is to be computed. This must be a NumPy array of samples of shape + `(number_of_samples, number_of_dimensions)`, in `number_of_dimensions` dimensional space. + + Returns: + Any: Returns a NumPy array, which contains a square distance matrix, where each element i, j contains the PCC between the samples i and j. + """ + + def pearsonr_distance(x: NDArray[Any], y: NDArray[Any]) -> float: + """Computes the Pearson correlation coefficient between two samples. + Args: + x (NDArray[Any]): The first sample. + y (NDArray[Any]): The second sample. + Returns: + float: The Pearson correlation coefficient between the two samples. + """ + + p_value: NDArray[numpy.floating] | float = pearsonr(x, y).statistic + if isinstance(p_value, numpy.ndarray): + return p_value.item() + return p_value + + input_data: NDArray[Any] = data + distance_matrix: NDArray[numpy.floating] = pdist(input_data, metric=pearsonr_distance) + return squareform(distance_matrix) + + +# Contains the various pre-processing method and distance metric variants that can be used to compute the analysis VARIANTS = { 'absspectral': { 'preprocessing': Sequential([ @@ -124,34 +231,53 @@ def function(self, data): } -@click.command() -@click.argument('attribution-file', type=click.Path()) -@click.argument('analysis-file', type=click.Path()) -@click.option('--n-clusters', type=csints, default=','.join(str(elem) for elem in range(2, 31))) -@click.option('--class-indices', type=csints) -@click.option('--label-map', 'label_map_file', type=click.Path()) -@click.option('--variant', type=click.Choice(list(VARIANTS)), default='spectral') -@click.option('--n-eigval', type=int, default=32) -@click.option('--n-neighbors', type=int, default=32) -def main(variant, attribution_file, analysis_file, class_indices, label_map_file, n_eigval, n_clusters, n_neighbors): - preprocessing = VARIANTS[variant]['preprocessing'] - distance = VARIANTS[variant]['distance'] - +def meta_analysis( + attribution_file_path: str, + analysis_file_path: str, + variant: str, + class_indices: list[int], + label_map_file_path: str, + number_of_eigenvalues: int, + number_of_clusters_list: list[int], + number_of_neighbors: int +) -> None: + """Performs a meta-analysis over the specified attribution data and writes the results into an analysis database. + + Args: + attribution_file_path (str): The path to the attribution database file, that contains the attributions for which the meta-analysis is to be + performed. + analysis_file_path (str): The path to the analysis database file, into which the results of the meta-analysis are to be written. + variant (str): The meta-analysis variant that is to be performed. Can be one of "absspectral", "spectral", "fullspectral", or "histogram". + class_indices (list[int]): The indices of the classes for which the meta-analysis is to be performed. If not specified, then the meta-analysis + is performed for all classes. + label_map_file_path (str): The path to the label map file, which contains a mapping between the class indices and their corresponding names + and WordNet IDs. + number_of_eigenvalues (int): The number of eigenvalues of the eigenvalue decomposition. + number_of_clusters_list (list[int]): A list that can contain multiple numbers of clusters. For each number of clusters in this list, all + clustering methods and the meta-analysis are performed. + number_of_neighbors (int): The number of neighbors that are to be considered in the k-nearest neighbor clustering algorithm. + """ + + # Determines the pre-processing pipeline and the distance metric that are to be used for the meta-analysis + pre_processing_pipeline = VARIANTS[variant]['preprocessing'] + distance_metric = VARIANTS[variant]['distance'] + + # Creates the meta-analysis pipeline pipeline = SpectralClustering( - preprocessing=preprocessing, - pairwise_distance=distance, - affinity=SparseKNN(n_neighbors=n_neighbors, symmetric=True), - embedding=EigenDecomposition(n_eigval=n_eigval, is_output=True), + preprocessing=pre_processing_pipeline, + pairwise_distance=distance_metric, + affinity=SparseKNN(n_neighbors=number_of_neighbors, symmetric=True), + embedding=EigenDecomposition(n_eigval=number_of_eigenvalues, is_output=True), clustering=Parallel([ Parallel([ - KMeans(n_clusters=k) for k in n_clusters + KMeans(n_clusters=number_of_clusters) for number_of_clusters in number_of_clusters_list ], broadcast=True), Parallel([ - DBSCAN(eps=k / 10.) for k in n_clusters + DBSCAN(eps=number_of_clusters / 10.0) for number_of_clusters in number_of_clusters_list ], broadcast=True), HDBSCAN(), Parallel([ - AgglomerativeClustering(n_clusters=k) for k in n_clusters + AgglomerativeClustering(n_clusters=number_of_clusters) for number_of_clusters in number_of_clusters_list ], broadcast=True), Parallel([ UMAPEmbedding(), @@ -160,75 +286,201 @@ def main(variant, attribution_file, analysis_file, class_indices, label_map_file ], broadcast=True, is_output=True) ) - if label_map_file is not None: - with open(label_map_file, 'r') as fp: - label_map = json.load(fp) - label_map = {elem['index']: elem['word_net_id'] for elem in label_map} + # Loads the label map and converts it to a dictionary, which maps the class index to its WordNet ID + if label_map_file_path is not None: + with open(label_map_file_path, 'r', encoding='utf-8') as label_map_file: + label_map = json.load(label_map_file) + wordnet_id_map = {label['index']: label['word_net_id'] for label in label_map} + class_name_map = {label['index']: label['name'] for label in label_map} else: - label_map = {} + label_map = [] + wordnet_id_map = {} + class_name_map = {} + + # Retrieves the labels of the samples + with h5py.File(attribution_file_path, 'r') as attributions_file: + labels = attributions_file['label'][:] + + # Gets the indices of the classes for which the meta-analysis is to be performed, if non were specified, the meta-analysis is performed for all + # classes + if class_indices is None: + class_indices = [int(label['index']) for label in label_map] - with h5py.File(attribution_file, 'r') as fp: - label = fp['label'][:] + # Truncate the analysis database + print(f'Truncating {analysis_file_path}') + h5py.File(analysis_file_path, 'w').close() + # Cycles through all classes and performs the meta-analysis for each of them for class_index in class_indices: - print('Loading class {:03d}'.format(class_index)) - with h5py.File(attribution_file, 'r') as fp: - index, = np.nonzero(label == class_index) - data = fp['attribution'][index, :] - if 'train' in fp: - train_flag = fp['train'][index.tolist()] + + # Loads the attribution data for the samples of the current class + print(f'Loading class {class_name_map[class_index]}') + with h5py.File(attribution_file_path, 'r') as attributions_file: + indices_of_samples_in_class, = numpy.nonzero(labels == class_index) + attribution_data = attributions_file['attribution'][indices_of_samples_in_class, :] + if 'train' in attributions_file: + train_flag = attributions_file['train'][indices_of_samples_in_class.tolist()] else: train_flag = None - print('Computing class {:03d}'.format(class_index)) - (eigenvalues, embedding), (kmeans, dbscan, hdbscan, agglo, (umap, tsne)) = pipeline(data) - - print('Saving class {:03d}'.format(class_index)) - with h5py.File(analysis_file, 'a') as fp: - analysis_name = label_map.get(class_index, '{:03d}'.format(class_index)) - g_analysis = fp.require_group(analysis_name) - g_analysis['index'] = index.astype('uint32') - - g_embedding = g_analysis.require_group('embedding') - g_embedding['spectral'] = embedding.astype('float32') - g_embedding['spectral'].attrs['eigenvalue'] = eigenvalues.astype('float32') - - g_embedding['tsne'] = tsne.astype('float32') - g_embedding['tsne'].attrs['embedding'] = 'spectral' - g_embedding['tsne'].attrs['index'] = np.array([0, 1]) - - g_embedding['umap'] = umap.astype('float32') - g_embedding['umap'].attrs['embedding'] = 'spectral' - g_embedding['umap'].attrs['index'] = np.array([0, 1]) - - g_cluster = g_analysis.require_group('cluster') - for n_cluster, clustering in zip(n_clusters, kmeans): - s_cluster = 'kmeans-{:02d}'.format(n_cluster) - g_cluster[s_cluster] = clustering - g_cluster[s_cluster].attrs['embedding'] = 'spectral' - g_cluster[s_cluster].attrs['k'] = n_cluster - g_cluster[s_cluster].attrs['index'] = np.arange(embedding.shape[1], dtype='uint32') - - for n_cluster, clustering in zip(n_clusters, dbscan): - s_cluster = 'dbscan-eps={:.1f}'.format(n_cluster / 10.) - g_cluster[s_cluster] = clustering - g_cluster[s_cluster].attrs['embedding'] = 'spectral' - g_cluster[s_cluster].attrs['index'] = np.arange(embedding.shape[1], dtype='uint32') - - s_cluster = 'hdbscan' - g_cluster[s_cluster] = hdbscan - g_cluster[s_cluster].attrs['embedding'] = 'spectral' - g_cluster[s_cluster].attrs['index'] = np.arange(embedding.shape[1], dtype='uint32') - - for n_cluster, clustering in zip(n_clusters, agglo): - s_cluster = 'agglomerative-{:02d}'.format(n_cluster) - g_cluster[s_cluster] = clustering - g_cluster[s_cluster].attrs['embedding'] = 'spectral' - g_cluster[s_cluster].attrs['k'] = n_cluster - g_cluster[s_cluster].attrs['index'] = np.arange(embedding.shape[1], dtype='uint32') - + # Performs the meta-analysis for the attributions of the current class + print(f'Computing class {class_name_map[class_index]}') + (eigenvalues, embedding), (kmeans, dbscan, hdbscan, agglomerative, (umap, tsne)) = pipeline(attribution_data) + + # Append the meta-analysis to the analysis database + print(f'Saving class {class_name_map[class_index]}') + with h5py.File(analysis_file_path, 'a') as analysis_file: + + # The name of the analysis is the name of the class + analysis_name = wordnet_id_map.get(class_index, f'{class_index:08d}') + + # Adds the indices of the samples in the current class to the analysis database + analysis_group = analysis_file.require_group(analysis_name) + analysis_group['index'] = indices_of_samples_in_class.astype('uint32') + + # Adds the spectral embedding to the analysis database + embedding_group = analysis_group.require_group('embedding') + embedding_group['spectral'] = embedding.astype(numpy.float32) + embedding_group['spectral'].attrs['eigenvalue'] = eigenvalues.astype(numpy.float32) + + # Adds the t-SNE embedding to the analysis database + embedding_group['tsne'] = tsne.astype(numpy.float32) + embedding_group['tsne'].attrs['embedding'] = 'spectral' + embedding_group['tsne'].attrs['index'] = numpy.array([0, 1]) + + # Adds the uMap embedding to the analysis database + embedding_group['umap'] = umap.astype(numpy.float32) + embedding_group['umap'].attrs['embedding'] = 'spectral' + embedding_group['umap'].attrs['index'] = numpy.array([0, 1]) + + # Adds the k-means clustering of the embeddings to the analysis database + cluster_group = analysis_group.require_group('cluster') + for number_of_clusters, clustering in zip(number_of_clusters_list, kmeans): + clustering_dataset_name = f'kmeans-{number_of_clusters:02d}' + cluster_group[clustering_dataset_name] = clustering + cluster_group[clustering_dataset_name].attrs['embedding'] = 'spectral' + cluster_group[clustering_dataset_name].attrs['k'] = number_of_clusters + cluster_group[clustering_dataset_name].attrs['index'] = numpy.arange( + embedding.shape[1], + dtype=numpy.uint32 + ) + + # Adds the DBSCAN epsilon clustering of the embeddings to the analysis database + for number_of_clusters, clustering in zip(number_of_clusters_list, dbscan): + clustering_dataset_name = f'dbscan-eps={number_of_clusters / 10.0:.1f}' + cluster_group[clustering_dataset_name] = clustering + cluster_group[clustering_dataset_name].attrs['embedding'] = 'spectral' + cluster_group[clustering_dataset_name].attrs['index'] = numpy.arange( + embedding.shape[1], + dtype=numpy.uint32 + ) + + # Adds the HDBSCAN clustering of the embeddings to the analysis database + clustering_dataset_name = 'hdbscan' + cluster_group[clustering_dataset_name] = hdbscan + cluster_group[clustering_dataset_name].attrs['embedding'] = 'spectral' + cluster_group[clustering_dataset_name].attrs['index'] = numpy.arange( + embedding.shape[1], + dtype=numpy.uint32 + ) + + # Adds the Agglomerative clustering of the embeddings to the analysis database + for number_of_clusters, clustering in zip(number_of_clusters_list, agglomerative): + clustering_dataset_name = f'agglomerative-{number_of_clusters:02d}' + cluster_group[clustering_dataset_name] = clustering + cluster_group[clustering_dataset_name].attrs['embedding'] = 'spectral' + cluster_group[clustering_dataset_name].attrs['k'] = number_of_clusters + cluster_group[clustering_dataset_name].attrs['index'] = numpy.arange( + embedding.shape[1], + dtype=numpy.uint32 + ) + + # If the attributions were computed on the training split of the dataset, then the training flag is set if train_flag is not None: - g_cluster['train_split'] = train_flag + cluster_group['train_split'] = train_flag + + +def main() -> None: + """The entrypoint to the virelay_analysis script.""" + + argument_parser = argparse.ArgumentParser( + prog='virelay_analysis', + description='''Performs a meta-analysis on an attribution database and writes them into an analysis database, from which a ViRelAy project can + be created.''' + ) + argument_parser.add_argument( + 'attribution_file_path', + type=str, + help='The path to the attribution database file, that contains the attributions for which the meta-analysis is to be performed.' + ) + argument_parser.add_argument( + 'analysis_file_path', + type=str, + help='The path to the analysis database file, into which the results of the meta-analysis are to be written.' + ) + argument_parser.add_argument( + '-V', + '--variant', + dest='variant', + type=str, + choices=['absspectral', 'spectral', 'fullspectral', 'histogram'], + default='spectral', + help='The meta-analysis variant that is to be performed. Defaults to "spectral".' + ) + argument_parser.add_argument( + '-c', + '--class-indices', + dest='class_indices', + type=int, + nargs='*', + help='''The indices of the classes for which the meta-analysis is to be performed. If not specified, then the meta-analysis is performed for + all classes.''' + ) + argument_parser.add_argument( + '-l', + '--label-map-file-path', + dest='label_map_file_path', + type=str, + help='The path to the label map file, which contains a mapping between the class indices and their corresponding names and WordNet IDs.' + ) + argument_parser.add_argument( + '-e', + '--number-of-eigenvalues', + dest='number_of_eigenvalues', + type=int, + default=32, + help='The number of eigenvalues of the eigenvalue decomposition. Defaults to 32.' + ) + argument_parser.add_argument( + '-n', + '--number-of-neighbors', + dest='number_of_neighbors', + type=int, + default=32, + help='The number of neighbors that are to be considered in the k-nearest neighbor clustering algorithm. Defaults to 32.' + ) + argument_parser.add_argument( + '-C', + '--number-of-clusters-list', + dest='number_of_clusters_list', + type=int, + nargs='*', + default=list(range(2, 31)), + help='''A list that can contain multiple numbers of clusters. For each number of clusters in this list, all clustering methods and the + meta-analysis are performed. Defaults to a list from 2 to 30 clusters.''' + ) + arguments = argument_parser.parse_args() + + meta_analysis( + arguments.attribution_file_path, + arguments.analysis_file_path, + arguments.variant, + arguments.class_indices, + arguments.label_map_file_path, + arguments.number_of_eigenvalues, + arguments.number_of_clusters_list, + arguments.number_of_neighbors + ) if __name__ == '__main__': diff --git a/source/corelay/__init__.py b/source/corelay/__init__.py index e69de29..d910efd 100644 --- a/source/corelay/__init__.py +++ b/source/corelay/__init__.py @@ -0,0 +1,4 @@ +"""CoRelAy is a package to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (``Task``) with +default operations (``Processor``). Any step of the pipeline may then be individually changed by assigning a new operator (``Processor``). Processors +have parameters (``Param``) which define their operation. +""" diff --git a/source/corelay/base.py b/source/corelay/base.py index be911a7..5e8c2a2 100644 --- a/source/corelay/base.py +++ b/source/corelay/base.py @@ -1,51 +1,58 @@ -"""Module containing basic CoRelAy classes, such as Param""" -from .plugboard import Slot +"""A module that contains basic CoRelAy classes, such as ``Param``.""" +from typing import Any + +from corelay.plugboard import Slot -class Param(Slot): - """A single parameter, which instances are to be tracked by a `MetaTracker`. - - Attributes - ---------- - dtype : type or tuple of type - Allowed type(s) of the parameter. - default : :obj:`dtype` - Default parameter value, should be an instance of (one of) :obj:`dtype`. - positional : bool - Optional, whether the Parameter can also be set as a positional Parameter. - identifier : bool - Optional, whether the Parameter should be used to identify a Processor. - - """ - def __init__(self, dtype, default=None, mandatory=False, positional=False, identifier=False): - """Configure type and default value of parameter. - - Parameters - ---------- - dtype : type or tuple of type - Allowed type(s) of the parameter. - default : :obj:`dtype` - Default parameter value, should be an instance of (one of) :obj:`dtype`. +class Param(Slot): + """A single parameter, whose instances are tracked by a ``MetaTracker``.""" + + def __init__( + self, + dtype: type | tuple[type, ...], + default: Any = None, + mandatory: bool = False, + positional: bool = False, + identifier: bool = False + ) -> None: + """Initializes a new ``Param`` instance, and configures its type and default value of the parameter. + + Args: + dtype (type | tuple[type, ...]): The allowed type(s) of the parameter. This can be a single type or a tuple of types. + default (Any): The default value of the parameter. This must be an instance of one of the types specified in ``dtype``. + mandatory (bool): A value indicating whether this parameter is mandatory. If `True`, the default value will be removed. + positional (bool): A value indicating whether this parameter will have to be passed as a positional argument to ``Processor.__init__``. If + `True`, the parameter will be passed as a positional argument to ``Processor.__init__``, otherwise it will be passed as a keyword + argument. + identifier (bool): A value indicating whether this parameter should be used to identify a ``Processor``. If `True`, the parameter will be + used to identify a ``Processor``. This is useful for distinguishing processors, when caching their outputs. """ + super().__init__(dtype, default) + if mandatory: del self.default + self._positional = positional self._identifier = identifier - # allowed_dtypes = (type, FunctionType, BuiltinFunctionType) - # if not all(isinstance(x, allowed_dtypes) for x in self.dtype): - # raise TypeError( - # "Following dtypes: {} are not in the allowed types {}.".format(self.dtype, allowed_dtypes) - # ) - @property - def is_positional(self): - """Whether this param can be assigned as a positional argument to Processor.__init__""" + def is_positional(self) -> bool: + """Gets or sets a value indicating whether this parameter can be assigned as a positional argument to ``Processor.__init__``. + + Returns: + bool: Returns `True` if this parameter can be assigned as a positional argument to ``Processor.__init__`` and `False` otherwise. + """ + return self._positional @property - def is_identifier(self): - """Whether this param should be used to identify a Processor""" + def is_identifier(self) -> bool: + """Gets or sets a value indicating whether this parameter should be used to identify a ``Processor``. + + Returns: + bool: Returns `True` if this parameter should be used to identify a ``Processor`` and `False` otherwise. + """ + return self._identifier diff --git a/source/corelay/io/__init__.py b/source/corelay/io/__init__.py index 7de25d8..2920183 100644 --- a/source/corelay/io/__init__.py +++ b/source/corelay/io/__init__.py @@ -1,8 +1,15 @@ -"""IO-related module for Processor data""" -from .storage import Storable, NoDataSource, NoDataTarget, DataStorageBase, NoStorage, PickleStorage, HDF5Storage +"""A sub-package containing IO-related modules for ``Processor`` data.""" + +from corelay.io.storage import ( + NoDataSource, + NoDataTarget, + DataStorageBase, + NoStorage, + PickleStorage, + HDF5Storage +) __all__ = [ - 'Storable', 'NoDataSource', 'NoDataTarget', 'DataStorageBase', diff --git a/source/corelay/io/hashing.py b/source/corelay/io/hashing.py index 61bc5c1..da8beda 100644 --- a/source/corelay/io/hashing.py +++ b/source/corelay/io/hashing.py @@ -1,47 +1,126 @@ -"""Persistent, non-cryptographic hashing of python objects. - -Note ----- -See https://github.com/chr5tphr/funcache/blob/main/funcache/hashing.py +"""A module that contains non-cryptographic hashing functionality for Python objects. +Note: + See https://github.com/chr5tphr/funcache/blob/master/funcache/hashing.py to see the original implementation of this module. """ + +import importlib import pickle +from types import ModuleType +from typing import Any, Protocol, runtime_checkable -import numpy as np +import numpy +from metrohash import MetroHash128 # pylint: disable = no-name-in-module from numpy import ndarray -# pylint: disable=no-name-in-module -from metrohash import MetroHash128 +from numpy.typing import NDArray + + +@runtime_checkable +class SupportsConversionToNumPyArray(Protocol): + """A protocol that defines an interface for objects that can be converted to a NumPy array.""" + + def numpy(self) -> NDArray[Any]: + """Converts the object to a NumPy array. + + Returns: + NDArray[Any]: Returns a NumPy array representation of the object. + """ + + +class TensorPlaceholder: + """A placeholder class to stand in for PyTorch's ``Tensor`` class in case PyTorch is not installed.""" + + def numpy(self) -> NDArray[Any]: + """Converts the PyTorch tensor to a NumPy array. + + Raises: + NotImplementedError: This method should not be called, as this is a placeholder class. + + Returns: + NDArray[Any]: Returns a NumPy array representation of the PyTorch tensor. + """ + + raise NotImplementedError('This method should not be called, as this is a placeholder class.') + + +Tensor: type[SupportsConversionToNumPyArray] +"""Either the PyTorch ``Tensor`` class or a placeholder class if PyTorch is not installed. + +Note: + This is used to check if an object that is to be pickled is a PyTorch tensor or not, because PyTorch ``Tensor`` objects are converted to NumPy + arrays before pickling. +""" + + +# Tries to import PyTorch and assign the Tensor class to the variable Tensor, if PyTorch is not installed, then the Tensor class will be replaced with +# a placeholder class try: - from torch import Tensor + torch: ModuleType = importlib.import_module('torch') + Tensor = torch.Tensor except ImportError: - class Tensor: - """Dummy Tensor""" + Tensor = TensorPlaceholder class Hasher(MetroHash128): """Hasher object with a write function for file-like updates""" - def write(self, data): - """Update using write for file-like behavior""" + + def write(self, data: bytes) -> int: + """Updates the hash, by adding the specified data to the end of the input. + + Note: + This method was made to give the ``Hasher`` object a file-like interface. + + Args: + data (bytes): The data to add to the hash. This can be any bytes-like object. + + Returns: + int: Returns the number of bytes added to the hash. + """ + self.update(data) return len(data) class HashPickler(pickle.Pickler): - """Pickler for computing Hashes""" + """A pickler for computing hashes.""" + @staticmethod - def numpy_id(obj): - """Persistent id for numpy arrays""" - mantissa, exponent = np.frexp(obj) - np.around(mantissa, decimals=2, out=mantissa) + def numpy_id(array: NDArray[Any]) -> tuple[str, tuple[int, ...], bytes, bytes]: + """Computes a unique ID for NumPy arrays, which consists of the data type name, the array's shape, and the values of the array decomposed into + their respective mantissas and exponents as a ``bytes`` sequence. + + Args: + array (NDArray[Any]): The NumPy array to compute the ID for. + + Returns: + tuple[str, tuple[int, ...], bytes, bytes]: A tuple containing the data type name, the array's shape, and the values of the array + decomposed into their respective mantissas and exponents as a ``bytes`` sequence. + """ + + mantissa, exponent = numpy.frexp(array) + numpy.around(mantissa, decimals=2, out=mantissa) return ( - obj.dtype.name, - obj.shape, + array.dtype.name, + array.shape, bytes(mantissa), bytes(exponent), ) - def persistent_id(self, obj): - """Persistent ids for persistent pickles""" + def persistent_id(self, obj: Any) -> tuple[str, tuple[int, ...], bytes, bytes] | None: + """Computes a persistent ID for an object that is to be pickled, which can be used by the ``pickle`` module to identify two objects as "the + same" during the un-pickling process. The persistent ID is used to identify the object in a way that is independent of its memory address. + This is useful for caching and serialization purposes. + + Args: + obj (Any): The object to compute the persistent ID for. + + Returns: + tuple[str, tuple[int, ...], bytes, bytes] | None: Returns a persistent ID for the object. If the object is a NumPy array, it returns a + tuple containing the data type name, the array's shape, and the values of the array decomposed into their respective mantissas and + exponents as a ``bytes`` sequence. If the object is a PyTorch tensor, it converts the tensor to a NumPy array and computes a unique ID + for the array. If the object is neither, it returns ``None``. + """ + if isinstance(obj, ndarray): return self.numpy_id(obj) if isinstance(obj, Tensor): @@ -49,8 +128,18 @@ def persistent_id(self, obj): return None -def ext_hash(data): - """Extended non-cryptographic Hashing using Pickle and MetroHash""" +def ext_hash(data: Any) -> str: + """Hashes the specified data. It uses an extended, non-cryptographic hashing algorithm, which first pickles the specified object and then hashes + the resulting ``bytes`` sequence using MetroHash. + + Args: + data (Any): The data to hash. This can be any Python object, including NumPy arrays and PyTorch tensors. + + Returns: + str: Returns the hash of the data as a hexadecimal string. + """ + hasher = Hasher() HashPickler(hasher).dump(data) - return hasher.hexdigest() + hash_value: str = hasher.hexdigest() + return hash_value diff --git a/source/corelay/io/storage.py b/source/corelay/io/storage.py index 496efba..da13f3f 100644 --- a/source/corelay/io/storage.py +++ b/source/corelay/io/storage.py @@ -1,349 +1,775 @@ -"""'io module contains classes to load and dump different files like hdf5, etc. - -""" +"""A module that contains classes to read and write different file formats like HDF5.""" import copy -import pickle import json +import pickle +from abc import ABC, abstractmethod from collections import OrderedDict -from abc import abstractmethod +from collections.abc import KeysView +from os import PathLike +from types import TracebackType +from typing import Annotated, Any, IO, Literal, NamedTuple, Protocol, runtime_checkable -import numpy as np import h5py +import numpy +from numpy.typing import NDArray + +from corelay.base import Param +from corelay.io.hashing import ext_hash +from corelay.plugboard import Plugboard -from ..base import Param -from ..plugboard import Plugboard -from .hashing import ext_hash +@runtime_checkable +class Storable(Protocol): + """An abstract class that defines the interface for storable objects, i.e., objects that have a ``read`` and ``write`` method.""" -class StorableMeta(type): - """Meta class to check for write/ read attributes via isinstance""" - def __instancecheck__(cls, instance): - """Is instance if object has attributes write and read""" - return all(hasattr(instance, attr) for attr in ('write', 'read')) + def read(self, data_in: Any, meta: Any) -> Any: + """Retrieves the output data that was produced by the specified input data if it is available. The meta data can contain additional + identifying information about the data. + Args: + data_in (Any): The input data to retrieve the output data for. + meta (Any): The meta data to retrieve the output data for, which can contain additional identifying information about the data. -class Storable(metaclass=StorableMeta): - """Abstract class to check for write/ read attributes via isinstance""" + Returns: + Any: The output data that was produced by the specified input data if it is available. + """ + + def write(self, data_out: Any, data_in: Any, meta: Any) -> None: + """Writes the specified output data to the storage container. The meta data that can be used to store additional identifying information about + the data. + + Args: + data_out (Any): The output data to write. + data_in (Any): The input data that produced the output data. + meta (Any): The meta data that can be used to store additional identifying information about the data. + """ class NoDataSource(Exception): - """Raise when no data source available.""" - # Following is not useless, since message becomes optional - # pylint: disable=useless-super-delegation - def __init__(self, message='No Data Source available.'): + """An exception, which is raised when no data source available.""" + + def __init__(self, message: str = 'No Data Source available.') -> None: + """Initializes a new ``NoDataSource`` instance. + + Args: + message (str): The error message to be displayed. Defaults to 'No Data Source available.'. + """ + super().__init__(message) class NoDataTarget(Exception): - """Raise when no target source available.""" - def __init__(self): + """An exception, which is raised when no target source available.""" + + def __init__(self) -> None: + """Initializes a new ``NoDataTarget`` instance.""" + super().__init__('No Data Target available.') +RecursiveNDArrayTuple = tuple['NDArray[Any] | RecursiveNDArrayTuple', ...] +"""A recursive tuple of NumPy arrays, i.e., a tuple that contains NumPy arrays or other tuples of NumPy arrays, which themselves can contain other +tuples of NumPy arrays, and so on. This is used to represent a nested structure of NumPy arrays. +""" + + +RecursiveHashTuple = tuple['str | RecursiveHashTuple', ...] +"""A recursive tuple of strings, i.e., a tuple that contains strings or other tuples of strings, which themselves can contain other tuples of strings, +and so on. This is used to represent a nested structure of hashes for the data that is stored in a ``RecursiveNDArrayTuple``. +""" + + class HashedHDF5: - """Hashed storage of Processor data in HDF5 files""" - def __init__(self, h5group): + """A storage container, which can be used to store ``Processor`` data in HDF5 files. A hash of the input data that produced the stored data is + stored alongside the data, so that the data can later be retrieved based on the input data. + """ + + base: h5py.Group + """The HDF5 group to store the data in.""" + + def __init__(self, h5group: h5py.Group) -> None: + """Initializes a new ``HashedHDF5`` instance. + + Args: + h5group (h5py.Group): The HDF5 group to store the data in. + """ + self.base = h5group - def read(self, data_in, meta): - """Read output from a hashed h5 group, with hash of (data_in, meta)""" - def _iterread(base): - """Iteratively read from HDF5 Group into tuple hierarchy of ndarrays""" - if isinstance(base, h5py.Group): - return tuple(_iterread(base[key]) for key in sorted(base)) - if isinstance(base, h5py.Dataset): - return base[()] - raise TypeError('Unsupported output type!') - hashval = ext_hash((data_in, meta)) + def read(self, data_in: Any, meta: Any) -> Any: + """Retrieves the output data that was produced by the specified input data if it is available. The hash is computed from the input data and + the meta data. The meta data can contain additional identifying information about the data. + + Args: + data_in (Any): The input data to retrieve the output data for. + meta (Any): The meta data to retrieve the output data for, which can contain additional identifying information about the data. + + Raises: + NoDataSource: The data source is not available. + + Returns: + Any: The output data that was produced by the specified input data if it is available. + """ + + hash_value = ext_hash((data_in, meta)) + try: + group = self.base[hash_value] + except KeyError as exception: + raise NoDataSource() from exception + + return HashedHDF5._read_hdf5_content_recursively(group['data']) + + def write(self, data_out: Any, data_in: Any, meta: Any) -> None: + """Writes the specified output data to a hashed HDF5 group. The hash is computed from the input data and the meta data. The meta data that can + be used to store additional identifying information about the data. + + Args: + data_out (Any): The output data to write. + data_in (Any): The input data that produced the output data. Is used to compute the hash. + meta (Any): The meta data that can be used to store additional identifying information about the data. Is used to compute the hash. + + Raises: + TypeError: The data type of the input data is not supported. + """ + + hash_value = ext_hash((data_in, meta)) + group = self.base.require_group(hash_value) + try: - group = self.base[hashval] - except KeyError as error: - raise NoDataSource() from error - - return _iterread(group['data']) - - def write(self, data_out, data_in, meta): - """Write output to a hashed h5 group, with hash of (data_in, meta)""" - def _iterwrite(data, group, elem): - """Iteratively write to a HDF5 Group from a tuple hierarchy of ndarrays""" - if isinstance(data, tuple): - g_new = group.require_group(elem) - for n, array in enumerate(data_out): - _iterwrite(array, g_new, f'{n:03d}') - elif isinstance(data, np.ndarray): - group[elem] = data - else: - raise TypeError('Unsupported output type!') - - def _iterhash(base): - """Iteratively hash from tuple hierarchy into tuple hierarchy of hashes""" - if isinstance(base, tuple): - return tuple(_iterhash(obj) for obj in base) - return ext_hash(base) - - hashval = ext_hash((data_in, meta)) - group = self.base.require_group(hashval) - _iterwrite(data_out, group, 'data') + HashedHDF5._write_hdf5_recursively(data_out, group, 'data') + except TypeError as exception: + raise TypeError( + f'The data type of the output data "{type(data_out)}" is not supported. The must either be a NumPy array or a hierarchy of tuples ' + 'containing NumPy arrays and tuples of NumPy arrays.' + ) from exception + group['meta'] = json.dumps(meta) - group['input'] = json.dumps(_iterhash(data_in)) - group['output'] = json.dumps(_iterhash(data_out)) + group['input'] = json.dumps(HashedHDF5._hash_data_recursively(data_in)) + group['output'] = json.dumps(HashedHDF5._hash_data_recursively(data_out)) + @staticmethod + def _read_hdf5_content_recursively(base: h5py.Group | h5py.Dataset) -> NDArray[Any] | RecursiveNDArrayTuple: + """Recursively goes through the hierarchy of HDF5 groups and converts the HDF5 datasets (which are the leaf nodes in this hierarchy) into + a hierarchy of nested tuples. The keys of the groups are sorted to ensure that the order of the data is consistent. -class DataStorageBase(Plugboard): - """Implements a key, value storage object. + Args: + base (h5py.Group | h5py.Dataset): The HDF5 group or dataset to read. + + Raises: + TypeError: The data type of the input data is not supported. + + Returns: + NDArray[Any] | RecursiveNDArrayTuple: Returns a tuple of NumPy arrays or other tuples of NumPy arrays, which themselves can be nested. + Each tuple represents an HDF5 group. Nested tuples appear in the alphabetical order of the keys of the corresponding HDF5 groups. + The NumPy arrays contain the data that was read from the HDF5 datasets contained as leaf nodes in the HDF5 groups. If the + specified ``base`` is a dataset, the data is directly returned as a NumPy array. + """ + + if isinstance(base, h5py.Group): + return tuple(HashedHDF5._read_hdf5_content_recursively(base[key]) for key in sorted(base)) + if isinstance(base, h5py.Dataset): + dataset_data: NDArray[Any] = base[()] + return dataset_data + raise TypeError('Unsupported output type!') + + @staticmethod + def _write_hdf5_recursively(data: RecursiveNDArrayTuple | NDArray[Any], group: h5py.Group, key: str) -> None: + """Recursively writes the specified data to an HDF5 group from a tuple hierarchy of NumPy arrays. The keys of the group are generated from + the indices of the content in the tuples. The NumPy arrays are stored as HDF5 datasets. + + Args: + data (RecursiveNDArrayTuple | NDArray[Any]): The data to write to the HDF5 group, which can either be a tuple containing NumPy arrays or + other tuples containing NumPy arrays or tuples of NumPy arrays, or a NumPy array. + group (h5py.Group): The HDF5 group to write the data to. + key (str): The key of the HDF5 group to write the data to. If the data is a tuple, the key is used to create a new group in the HDF5 + parent ``group``. The children of the tuple will then be written recursively to the newly created group and their key will be + generated using the index of the child inside of the tuple. If the data is a NumPy array, the key is used to create a new dataset + in the HDF5 parent ``group``. + + Raises: + TypeError: The data type of the input data is not supported. + """ + + if isinstance(data, tuple): + new_group = group.require_group(key) + for index, array in enumerate(data): + HashedHDF5._write_hdf5_recursively(array, new_group, f'{index:03d}') + elif isinstance(data, numpy.ndarray): + group[key] = data + else: + raise TypeError( + f'The data type of the output data "{type(data)}" is not supported. The must either be a NumPy array or a hierarchy of tuples ' + 'containing NumPy arrays and tuples of NumPy arrays.' + ) + + @staticmethod + def _hash_data_recursively(data: tuple[Any, ...] | Any) -> str | RecursiveHashTuple: + """Hashes the specified data recursively. If the data is a tuple, then the elements of the tuple are hashed recursively and the resulting + hashes are returned as a tuple. For all other data types, the hash is computed directly and returned. + + Args: + data (tuple[Any, ...] | Any): The data to hash. This can either be a tuple containing multiple elements, which themselves can be + tuples containing the data in a hierarchy, or it can be a single element. + + Returns: + str | RecursiveHashTuple: The hash of the data. If the data is a tuple, then the hashes of the elements are returned as a tuple. For + all other data types, the hash is computed directly and returned as a string. + """ + + if isinstance(data, tuple): + return tuple(HashedHDF5._hash_data_recursively(element) for element in data) + return ext_hash(data) + + +class DataStorageBase(ABC, Plugboard): + """The abstract base class for key-value stores.""" + + io: Any + """The storage object to read and write data to. Defaults to `None`.""" + + def __init__(self, **kwargs: Any) -> None: + """Initializes a new ``DataStorageBase`` instance. + + Args: + **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., ``Plugboard``. + """ - """ - def __init__(self, **kwargs): super().__init__(**kwargs) - self.io = None + + self.io: Any = None @abstractmethod - def read(self, data_in=None, meta=None): - """Should implement read functionality. + def read(self, data_in: Any = None, meta: Any = None) -> Any: + """Reads the output data that was produced by the specified input data, if it is available. The meta data can contain additional identifying + information about the data. + + Args: + data_in (Any, optional): Input data that produces the data that is to be read. Defaults to `None`. + meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + Raises: + NoDataSource: The data source is not available. + + Returns: + Any: Returns the data that was produced by the specified input data if it is available. """ @abstractmethod - def write(self, data_out, data_in=None, meta=None): - """Should implement write functionality. - + def write(self, data_out: Any, data_in: Any = None, meta: Any = None) -> None: + """Writes the specified output data to the storage. The hash is computed from the input data and the meta data. The meta data can be used to + store additional identifying information about the data. + + Args: + data_out (Any): The output data to write. + data_in (Any, optional): The input data that produced the output data. Defaults to `None`. + meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. """ - def close(self): - """Close opened io file object. + @abstractmethod + def exists(self) -> bool: + """Checks if the data if data exists. + Returns: + bool: Returns `True` if the data exists and `False` otherwise. """ - self.io.close() @abstractmethod - def exists(self): - """Return True if data exists. + def keys(self) -> KeysView[str]: + """Retrieves the keys of the data stored in the storage container. + Returns: + KeysView[str]: Returns a list of keys of the io file object. """ - @abstractmethod - def keys(self): - """Return keys of the io file object. + def __enter__(self) -> 'DataStorageBase': + """Opens the IO object and returns the instance. This is used to implement the context manager protocol, which allows the use of the `with` + statement to automatically close the IO object when it is no longer needed. This is useful for ensuring that the IO object is properly closed + and resources are released when the context manager exits. + Returns: + DataStorageBase: Returns this instance of the ``DataStorageBase`` class. """ - def __enter__(self): return self - def __exit__(self, typ, value, traceback): - self.io.close() + def __exit__(self, exception_type: type[Exception] | None, exception: Exception, traceback: TracebackType | None) -> None: + """Closes the IO object. This is used to implement the context manager protocol, which allows the use of the `with` statement to automatically + close the IO object when it is no longer needed. This is useful for ensuring that the IO object is properly closed and resources are released + when the context manager exits. + + Args: + exception_type (type[Exception] | None): When the context manager exits due to an exception, this is the type of the exception that was + raised, otherwise it is ``None``. + exception (Exception): When the context manager exits due to an exception, this is the exception that was raised, otherwise it is + ``None``. + traceback (TracebackType | None): When the context manager exits due to an exception, this is the traceback of the exception that was + raised, otherwise it is ``None``. + """ + + if self.io is not None: + self.io.close() + + def __contains__(self, key: str) -> bool: + """Check if the key exists in the storage. + + Args: + key (str): The key to check for existence. + + Raises: + TypeError: The key is not a string. + + Returns: + bool: Returns 'True' if the key exists in the storage and `False` otherwise. + """ + + if not isinstance(key, str): + raise TypeError(f'The specified key "{key}" is not a string.') - def __contains__(self, key): return self.at(data_key=key).exists() - def __getitem__(self, key): + def __getitem__(self, key: str) -> Any: + """Get the data for a given key. + + Args: + key (str): The key to get the data for. + + Raises: + TypeError: The key is not a string. + + Returns: + Any: Returns the data for the given key. + """ + + if not isinstance(key, str): + raise TypeError(f'The specified key "{key}" is not a string.') + return self.at(data_key=key).read() - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any) -> None: + """Set the data for a given key. + + Args: + key (str): The key to set the data for. + value (Any): The data to set for the given key. + + Raises: + TypeError: The key is not a string. + """ + + if not isinstance(key, str): + raise TypeError(f'The specified key "{key}" is not a string.') + return self.at(data_key=key).write(value) - def __bool__(self): + def __bool__(self) -> bool: + """Converts the data storage object to a boolean value. This is used to determine if the data storage object is actually backed by a store. + + Returns: + bool: Returns `True` if the data storage object is backed by a store and `False` otherwise. + """ + return bool(self.io) - def at(self, **kwargs): - """Return a copy of the instance where kwargs become the attributes of the class. - I.e. a specific self.data_key is set so that self.write(data) automatically writes the data to correct key. + def close(self) -> None: + """Close opened io file object.""" + if self.io is not None: + self.io.close() + + def at(self, **kwargs: Any) -> 'DataStorageBase': + """Returns a copy of the instance where the keyword arguments were added as attributes of the class become the attributes of the class. + + Args: + **kwargs (Any): The keyword arguments, which are added as attributes of the class. + + Raises: + TypeError: One or more of the names in the keyword arguments are not valid attribute names. + + Returns: + DataStorageBase: Returns a copy of the instance where the keyword arguments were added as attributes of the class become the attributes + of the class. This allows to create a new instance of the class with new or updated attributes without modifying the original + instance. """ + result = copy.copy(self) for key, value in kwargs.items(): try: setattr(result.default, key, value) - except AttributeError as err: - raise TypeError( - f"'{key}' is an invalid keyword argument for '{type(self).__name__}.at'." - ) from err + except AttributeError as exception: + raise TypeError(f"'{key}' is an invalid keyword argument for '{type(self).__name__}.at'.") from exception return result class NoStorage(DataStorageBase): - """Stub class when no Storage is used.""" - def __bool__(self): + """A placeholder data storage class, which does not actually use persistent storage and raises exceptions when trying to read from it or write to + it. + """ + + def __bool__(self) -> bool: + """Converts the data storage object to a boolean value. This is used to determine if the data storage object is actually backed by a store. + + Returns: + bool: Returns `False` since this is a placeholder data storage class and does not actually use persistent storage. + """ + return False - def read(self, data_in=None, meta=None): + def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable=unused-argument + """Reads the output data that was produced by the specified input data, if it is available. The meta data can contain additional identifying + information about the data. + + Args: + data_in (Any, optional): Input data that produces the data that is to be read. Defaults to `None`. + meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + + Raises: + NoDataSource: This is a placeholder data storage class and does not actually use persistent storage and therefore always raises this + exception. + + Returns: + Any: The data that was produced by the specified input data if it is available. + """ + raise NoDataSource() - def write(self, data_out, data_in=None, meta=None): + def write(self, data_out: Any, data_in: Any = None, meta: Any = None) -> None: # pylint: disable=unused-argument + """Writes the specified output data to the storage. The meta data can be used to store additional identifying information about the data. + + Args: + data_out (Any): The output data to write. + data_in (Any, optional): The input data that produced the output data. Defaults to `None`. + meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. + + Raises: + NoDataTarget: This is a placeholder data storage class and does not actually use persistent storage and therefore always raises this + exception. + """ + raise NoDataTarget() - def exists(self): + def exists(self) -> bool: + """Returns True if data exists. + + Raises: + NoDataSource: This is a placeholder data storage class and does not actually use persistent storage and therefore always raises this + exception. + + Returns: + bool: Returns `False` since this is a placeholder data storage class and does not actually use persistent storage. + """ + raise NoDataSource() - def keys(self): + def keys(self) -> KeysView[str]: + """Retrieves the keys of the data stored in the storage container. + + Raises: + NoDataSource: This is a placeholder data storage class and does not actually use persistent storage and therefore always raises this + exception. + + Returns: + KeysView[str]: Returns never, since this is a placeholder data storage class that does not actually use persistent storage and raises an + exception. + """ + raise NoDataSource() +FileOpenMode = Literal['w', 'r', 'a'] +"""The file open mode to use when opening a file. The options are: + +- "w": Write mode. The file is created if it does not exist and existing files will be overwritten. +- "r": Read mode. The file must already exist and the data is read from the file. +- "a": Append mode. The file is created if it does not exist and the data is appended to the end of the file if the file already exists. +""" + + class PickleStorage(DataStorageBase): - """Experimental pickle storage that uses pickle to store data. + """Experimental pickle storage that uses the ``pickle`` module to store data.""" + + io: IO[Any] + """The file object to read data from and write data to. This is a binary file object that is used to store the pickled data.""" + data: dict[str, Any] + """A dictionary that stores the data that is read from or written to the file. The keys of the dictionary are the keys of the data that is stored + in the file, and the values are the data that is stored in the file. The dictionary is used to cache the data that is read from the file, so + that it does not need to be read from the file again if it is already cached. """ - data_key = Param(str, 'data', mandatory=True) + data_key: Annotated[str, Param(str, 'data', mandatory=True)] + """The key of the data that is read from the pickle file or written to the pickle file.""" - def __init__(self, path, mode='r', **kwargs): + def __init__(self, path: str | PathLike[str], mode: FileOpenMode = 'r', data_key: str | None = None, **kwargs: Any) -> None: """ - Parameters - ---------- - path: str - Path to the pickled file. - mode: str - Write, Read or Append mode ['w', 'r', 'a']. - + Initializes a new ``PickleStorage`` instance. + + Args: + path (str | PathLike[str]): The path to the pickle file where the data is to read from or written to. + mode (FileOpenMode, optional): The mode in which the file is opened. This can be either "w" for write mode, "r" for read mode or "a" for + append mode. In write mode, the file is created if it does not exist and the existing file is overwritten. In read mode, the file must + already exist and the data is read from the file. In append mode, the file is created if it does not exist and the data is appended to + the end of the file. Defaults to "r". + data_key (str | None): The key of the data that is read from the pickle file or written to the pickle file. Defaults to `None`. + **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., + ``DataStorageBase``. + + Raises: + ValueError: The mode is not "w", "r", or "a". """ + + # PyDocLint does not support the documentation of the constructor parameters both in the __init__ method and the class docstring, so we have + # to add the documentation for the data_key parameter here; therefore, the data_key parameter has to be added to the keyword arguments for the + # base class manually + if data_key is not None: + kwargs['data_key'] = data_key super().__init__(**kwargs) + if mode not in ['w', 'r', 'a']: - raise ValueError("Mode should be set to 'w', 'r' or 'a'.") - self.io = open(path, mode + 'b') # pylint: disable=consider-using-with - self.data = {} + raise ValueError('Mode should be set to "w", "r", or "a".') + + self.io: IO[Any] = open(path, mode + 'b') # pylint: disable=unspecified-encoding, consider-using-with + self.data: dict[str, Any] = {} + + def _load_data(self) -> None: + """Loads the data from the pickle file into the data dictionary. This is done by reading the file until the end of the file is reached and + unpickling each object. This method must be called before the data can be accessed, because, unlike in some other ways of storing the data, + pickle files are not random access. After the pickle file has be loaded, the data is cached in memory. + """ - def _load_data(self): try: while True: - dc = pickle.load(self.io) - self.data.update({dc['key']: dc['data']}) + loaded_data = pickle.load(self.io) + self.data.update({loaded_data['key']: loaded_data['data']}) except EOFError: pass - def read(self, data_in=None, meta=None): - """Return data for a given key. Need to load the complete pickle at first read. After the data is cached. + def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable=unused-argument + """Retrieves the data for a given data key. - Returns - ------- - data for a given key + Args: + data_in (Any, optional): Input data that produced the data that is to be read. Defaults to `None`. + meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + Raises: + NoDataSource: The data source for the given data key does not exist. + + Returns: + Any: Returns the data for the given data key. """ + + # The exists method will call keys which in turn will call _load_data, this way, the data will be lazy-loaded if not self.exists(): raise NoDataSource(f"Key: '{self.data_key}' does not exist.") return self.data[self.data_key] - def write(self, data_out, data_in=None, meta=None): - """Write and pickle the data as: {"data": data, "key": key} - - Parameters - ---------- - data_out: np.ndarray, dict - Data being stored. + def write(self, data_out: Any, data_in: Any = None, meta: Any = None) -> None: + """Writes the specified output data to the pickle file using the given data key as: `{'data': data_out, 'key': self.data_key}`. + Args: + data_out (Any): The data to write to the pickle file. + data_in (Any, optional): The input data that produced the output data. Defaults to `None`. + meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. """ + self.data[self.data_key] = data_out pickle.dump({"data": data_out, "key": self.data_key}, self.io) - def keys(self): - """Return keys from self.data. Need to load the complete pickle at first read. + def exists(self) -> bool: + """Determines if the data key exists in the data. + + Returns: + bool: Returns `True` if the data key exists and `False` otherwise. + """ + + # The keys will call _load_data this way, the data will be lazy-loaded + return self.data_key in self.keys() + + def keys(self) -> KeysView[str]: + """Retrieves the keys of the data stored in the pickle file. + Returns: + KeysView[str]: Returns a view of keys of the data that is stored in the file. """ + if not self.data: self._load_data() return self.data.keys() - def exists(self): - """Return True if key exists in self.keys(). - """ - return self.data_key in self.keys() +class StringInfo(NamedTuple): + """A type for the type information that the ``h5py.check_string_dtype`` function returns. This class is, unfortunately, not exported by the + ``h5py`` module, so we have to define it ourselves to gain type safety. + """ + encoding: str + """The encoding of the string, e.g., "utf-8" or "ascii".""" -class HDF5Storage(DataStorageBase): - """HDF5 storage that stores data under different keys. + length: int + """The length of the string.""" - """ - data_key = Param(str, 'data', mandatory=True) - def __init__(self, path, mode='r', **kwargs): +class HDF5Storage(DataStorageBase): + """A storage that used HDF5 files to store data.""" + + io: h5py.File + """The HDF5 file object to read data from and write data to.""" + + data_key: Annotated[str, Param(str, mandatory=True)] + """The key of the data that is read from the HDF5 file or written to the HDF5 file.""" + + def __init__(self, path: str | PathLike[str], mode: FileOpenMode = 'r', data_key: str | None = None, **kwargs: Any) -> None: + """Initializes a new ``HDF5Storage`` instance. + + Args: + path (str | PathLike[str]): The path to the HDF5 file where the data is to read from or written to. + mode (FileOpenMode, optional): The mode to open the HDF5 file in. This can be either "w" for write mode, "r" for read mode or "a" for + append mode. In write mode, the file is created if it does not exist and existing files will be overwritten. In read mode, the file + must already exist and the data is read from the file. In append mode, the file is created if it does not exist and the data is + appended to the end of the file if the file already exists. Defaults to "r". + data_key (str | None): The key of the data that is read from the HDF5 file or written to the HDF5 file. Defaults to `None`. + **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., + ``DataStorageBase``. """ - Parameters - ---------- - path: str - Path to the hdf5 file. - mode: str - Write, Read or Append mode ['w', 'r', 'a']. - """ + # PyDocLint does not support the documentation of the constructor parameters both in the __init__ method and the class docstring, so we have + # to add the documentation for the data_key parameter here; therefore, the data_key parameter has to be added to the keyword arguments for the + # base class manually + if data_key is not None: + kwargs['data_key'] = data_key super().__init__(**kwargs) - self.io = h5py.File(path, mode=mode) - def read(self, data_in=None, meta=None): - """ - Returns - ------- - data for a given key + self.io: h5py.File = h5py.File(path, mode=mode) + + def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable=unused-argument + """Retrieves the data for a given data key. + + Args: + data_in (Any, optional): Input data that produced the data that is to be read. Defaults to `None`. + meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + Raises: + NoDataSource: The data source for the given data key does not exist. + + Returns: + Any: Returns the data for the given data key. """ + if not self.exists(): raise NoDataSource(f"Key: '{self.data_key}' does not exist.") _, data = self._unpack('/', self.io[self.data_key]) return data - def write(self, data_out, data_in=None, meta=None): + def write(self, data_out: dict[str, Any] | tuple[Any, ...] | Any, data_in: Any = None, meta: Any = None) -> None: + """Writes the specified output data to the HDF5 file. If the output data is a dictionary, then the output data is stored in an HDF5 group with + the name given by the data key. The key-value pairs of the dictionary will be stored in this HDF5 group with the keys of the dictionary used + as the names of the datasets and the values of the dictionary used as the data for the datasets. If the output data is a tuple, then the + output data is stored in an HDF5 group with the name given by the data key. The values of the tuple will be stored as datasets in this HDF5 + group, with the indices of the tuple used as the names of the datasets and the values of the tuple used as the data for the datasets. If the + output data is neither a dictionary nor a tuple, then the output data is stored in an HDF5 dataset with the name given by the data key and the + output data used as the data for the dataset. + + Args: + data_out (dict[str, Any] | tuple[Any, ...] | Any): The data to write to the HDF5 file. This can either be a dataset, a tuple, or any value + that can be written to an HDF5 file (i.e., basic data types like ``int``, ``float``, ``bool``, or ``str``, or a NumPy array). If the + data is a dictionary, then it will be stored as an HDF5 group with the name given by the data key. The key-value pairs of the + dictionary will be stored in this HDF5 group with the keys of the dictionary used as the names of the datasets and the values of the + dictionary used as the data for the datasets. If the data is a tuple, then it will be stored as an HDF5 group with the name given by + the data key. The values of the tuple will be stored as datasets in this HDF5 group, with the indices of the tuple used as the names + of the datasets and the values of the tuple used as the data for the datasets. If the data is neither a dictionary nor a tuple, then + it will be stored in an HDF5 dataset with the name given by the data key and the data used as the data for the dataset. + data_in (Any, optional): The input data that produced the output data. Defaults to `None`. + meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. """ - Parameters - ---------- - data_out: np.ndarray, dict - Data being stored. Dictionaries are pickled and stored as strings. - """ if isinstance(data_out, dict): for key, value in data_out.items(): - shape, dtype = self._get_shape_dtype(value) - self.io.require_dataset(data=value, shape=shape, dtype=dtype, name=f'{self.data_key}/{key}') + shape, dtype = self._get_shape_and_dtype(value) + self.io.require_dataset(name=f'{self.data_key}/{key}', data=value, shape=shape, dtype=dtype) elif isinstance(data_out, tuple): for key, value in enumerate(data_out): - shape, dtype = self._get_shape_dtype(value) - self.io.require_dataset(data=value, shape=shape, dtype=dtype, name=f'{self.data_key}/{key}') + shape, dtype = self._get_shape_and_dtype(value) + self.io.require_dataset(name=f'{self.data_key}/{key}', data=value, shape=shape, dtype=dtype) else: - shape, dtype = self._get_shape_dtype(data_out) - self.io.require_dataset(data=data_out, shape=shape, dtype=dtype, name=self.data_key) + shape, dtype = self._get_shape_and_dtype(data_out) + self.io.require_dataset(name=self.data_key, data=data_out, shape=shape, dtype=dtype) - def exists(self): - """Returns True if key exists in self.io. + def exists(self) -> bool: + """Checks if the data key exists in the HDF5 file. + Returns: + bool: Returns `True` if the data key exists and `False` otherwise. """ + return self.data_key in self.io - def keys(self): - """Return keys of the storage. + def keys(self) -> KeysView[str]: + """Retrieves the keys of the data stored in the HDF5 file. + Returns: + KeysView[str]: Returns a view of keys of the data in the HDF5 file. """ - return self.io.keys() + + key_list: KeysView[str] = self.io.keys() + return key_list @staticmethod - def _unpack(key, value): - if key.isdigit(): + def _unpack(key: str | int, value: h5py.Dataset | h5py.Group | Any) -> tuple[str | int, Any]: + """Unpacks the specified value. If the value is an HDF5 dataset, it is converted to a NumPy array or a string, depending on the data type of + the dataset. If the value is a group, then it is recursively unpacked into an ordered dictionary, which contains the keys and values of the + group. The keys of the group are sorted to ensure that the order of the data is consistent. If the key is a string and only contains numeric + characters, it is converted to an integer. This is done to allow the use of the dictionary as a tuple or list. + + Args: + key (str | int): The key of the value to unpack. This can either be a string or an integer. If the key is a string and only contains + numeric characters, it is converted to an integer. + value (h5py.Dataset | h5py.Group | Any): The value that is to be unpacked. If the value is an HDF5 dataset, it is converted to a NumPy + array or a string, depending on the data type of the dataset. If the value is a group, then it is recursively unpacked into an ordered + dictionary, which contains the keys and values of the group. The keys of the group are sorted to ensure that the order of the data is + consistent. + + Returns: + tuple[str | int, Any]: Returns the unpacked key and value as a tuple. The key is either a string or an integer. The value is either a + NumPy array, a string, or an ordered dictionary, which contains the keys and values of the group. The keys of the group are sorted to + ensure that the order of the data is consistent. + """ + + # Converts the key to an integer if it is a string and only contains numeric characters + if isinstance(key, str) and key.isdigit(): key = int(key) + + # If the value is a dataset, it is converted to a NumPy array; if the data type of the dataset is a string, then it is decoded to a string + # using the encoding that was used to create the dataset if isinstance(value, h5py.Dataset): - check = h5py.check_string_dtype(value.dtype) + string_info: StringInfo | None = h5py.check_string_dtype(value.dtype) value = value[()] - if check is not None: - value = value.decode(check.encoding) + if string_info is not None: + value = value.decode(string_info.encoding) + + # If the value is a group, it is converted to an ordered dictionary, which contains the keys and values of the group; since the numeric keys + # are converted to an integer, the dictionary can be accessed like a tuple or list elif isinstance(value, h5py.Group): - # Change key to integer if k is digit, so that we can use the dict like a tuple or list value = OrderedDict((HDF5Storage._unpack(k, v) for k, v in value.items())) + + # Returns the key and value as a tuple return key, value @staticmethod - def _get_shape_dtype(value): - """Infer shape and dtype of given element v. + def _get_shape_and_dtype(value: NDArray[Any] | str | int | float) -> tuple[tuple[int, ...], numpy.dtype[Any]]: + """Infers the shape and data type of the specified value. - Parameters - ---------- - value: np.ndarray, str, int, float - Element for which we want to infer the shape and dtype. - - Returns - ------- - shape, dtype: tuple, type - Return the shape and dtype of v that works with h5py.require_dataset + Args: + value (NDArray[Any] | str | int | float): The value for which to infer the shape and data type. This can be a NumPy array, a string, an + integer, or a float. + Returns: + tuple[tuple[int, ...], numpy.dtype[Any]]: Returns a tuple containing the shape and data type of the specified value. The shape is a tuple + of integers representing the dimensions of the value. If the value is a NumPy array, the shape is the shape of the array. If the value + is of any other data type, the shape will be an empty tuple. """ - if isinstance(value, np.ndarray): + + if isinstance(value, numpy.ndarray): return value.shape, value.dtype if isinstance(value, str): return (), h5py.string_dtype(encoding='utf-8') - return (), np.dtype(type(value)) + return (), numpy.dtype(type(value)) diff --git a/source/corelay/pipeline/__init__.py b/source/corelay/pipeline/__init__.py index 3a35f43..525ed0a 100644 --- a/source/corelay/pipeline/__init__.py +++ b/source/corelay/pipeline/__init__.py @@ -1 +1 @@ -'''Base and built-in Pipelines.''' +"""A sub-package containing base and built-in pipelines.""" diff --git a/source/corelay/pipeline/base.py b/source/corelay/pipeline/base.py index cac4d5e..cdb4a7e 100644 --- a/source/corelay/pipeline/base.py +++ b/source/corelay/pipeline/base.py @@ -1,172 +1,292 @@ -"""Base classes Task and Pipeline. +"""A module that contains the base classes for tasks and pipeline.""" -""" from collections import OrderedDict +from collections.abc import Callable +from typing import Any -from ..processor.base import ensure_processor, Processor -from ..plugboard import Slot, Plug +from corelay.plugboard import Slot, Plug +from corelay.processor.base import ensure_processor, Processor class TaskPlug(Plug): - """Plug to ensure all contained objects are Processors""" - def __init__(self, slot, obj=None, default=None, **kwargs): + """A task plug, which ensures that all contained objects are processors.""" + + def __init__( + self, + slot: Slot, + obj: Processor | Callable[..., Any] | None = None, + default: Processor | Callable[..., Any] | None = None, + **kwargs: Any + ) -> None: + """Initializes a new ``TaskPlug`` instance. + + Args: + slot (Slot): Slot instance to associate with this ``TaskPlug``. + obj (Processor | Callable[..., Any] | None, optional): A processor held in the ``TaskPlug`` container. If not set, ``default`` is returned + as its value. Defaults to `None`. + default (Processor | Callable[..., Any] | None, optional): A plug-dependent lower-priority processor held in the ``TaskPlug`` container. + If not set, ``fallback`` is returned. Defaults to `None`. + **kwargs (Any): Keyword arguments passed down to the base class constructor, for cooperativity's sake, which is the next class in the + inheritance hierarchy. + """ + if default is not None: default = ensure_processor(default, **kwargs) if obj is not None: obj = ensure_processor(obj) + super().__init__(slot, obj=obj, default=default, **kwargs) - # pylint: disable=no-member - @Plug.obj.setter - def obj(self, value): + @property + def obj(self) -> Processor | None: + """Gets or sets the processor contained in the ``TaskPlug``. If the ``TaskPlug`` does not contain a processor, ``default`` is retrieved + instead. + + Note: + Actually, only the setter of the ``obj`` property needs to be overridden. The proper way of overriding the setter of the ``obj`` property + is to use the `@Plug.obj.setter` idiom, but this, unfortunately, is detected as a false positive by MyPy. Although, they have promised to + support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used very often in + the wild. Their workaround solution is to also override the getter and then use the new getter to override the setter and the deleter. I + have tested it, only in the getter, `super().obj` can be used, as it is not, yet, overridden, but in the setter and deleter, the complete + function must be re-implemented. Also, when the getter is overridden, not only the setter, but also the deleter must be overridden, as + otherwise, it will not be available anymore. + + Returns: + Processor | None: Returns the processor contained in the ``TaskPlug``. If not set, ``default`` is returned. + """ + + processor: Processor | None = super().obj + return processor + + @obj.setter + def obj(self, value: Processor | Callable[..., Any] | None) -> None: + """Gets or sets the processor contained in the ``TaskPlug`` and checks for consistency. It is ensured first, that the value is a + ``Processor``. If not, it is converted to a ``Processor`` using the ``ensure_processor`` function. + + Args: + value (Processor | Callable[..., Any] | None): The processor to set. + + Raises: + TypeError: The processor is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. + """ + if value is not None: value = ensure_processor(value) - # pylint: disable=no-member - Plug.obj.fset(self, value) + self._obj = value + + try: + self._consistent() + except TypeError as exception: + raise TypeError('The processor is not consistent with the dtype.') from exception + + @obj.deleter + def obj(self) -> None: + """Deletes the processor contained in the ``TaskPlug`` by setting it to `None`.""" + + self.obj = None + + @property + def default(self) -> Any: + """Gets or sets the default processor of the ``TaskPlug``. If the ``default`` processor is not set, then the ``fallback`` processor is + retrieved instead. + + Note: + Actually, only the setter of the ``default`` property needs to be overridden. The proper way of overriding the setter of the ``default`` + property is to use the `@Plug.default.setter` idiom, but this, unfortunately, is detected as a false positive by MyPy. Although, they have + promised to support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used + very often in the wild. Their workaround solution is to also override the getter and then use the new getter to override the setter and + the deleter. I have tested it, only in the getter, `super().default` can be used, as it is not, yet, overridden, but in the setter and + deleter, the complete function must be re-implemented. Also, when the getter is overridden, not only the setter, but also the deleter must + be overridden, as otherwise, it will not be available anymore. + + Returns: + Any: Returns the default processor of the ``TaskPlug``. If not set, ``fallback`` is returned. + """ + + return super().default + + @default.setter + def default(self, value: Any) -> None: + """Gets or sets the default processor of the ``TaskPlug`` and checks for consistency. It is ensured first, that the new default value is a + ``Processor``. If not, it is converted to a ``Processor`` using the ``ensure_processor`` function. + + Args: + value (Any): The new default processor to set. + + Raises: + TypeError: The default processor is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. + """ - # pylint: disable=no-member - @Plug.default.setter - def default(self, value): if value is not None: value = ensure_processor(value) - # pylint: disable=no-member - Plug.default.fset(self, value) + self._default = value + try: + self._consistent() + except TypeError as exception: + raise TypeError('The default processor is not consistent with the dtype.') from exception -class Task(Slot): - """A single item in a :obj:`Pipeline` task scheme. Tasks are slots that ensure all contained objects in Plugs and - own default values are Processors. - - Attributes - ---------- - proc_type : type - Class of the :obj:`Processor` allowed for this :obj:`Task`. - default : :obj:`Processor` or :obj:`types.FunctionType` or :obj:`types.MethodType` - Default :obj:`Processor` to use if no Processor is assigned. If a :obj:`types.FunctionType` or - :obj:`types.MethodType`, an appropriate :obj:`FunctionProcessor` will be created. - proc_kwargs : dict - Keyword arguments to overwrite on the supplied Processor. + @default.deleter + def default(self) -> None: + """Deletes the default processor of the ``TaskPlug`` by setting it to `None`.""" + + self.default = None + +class Task(Slot): + """Represents a single task in a ``Pipeline``. Tasks are slots that ensure all contained objects are plugs and own default values that are + processors. """ - def __init__(self, proc_type=Processor, default=(lambda data: data), **kwargs): - """Configure :obj:`Task` instance. - - Parameters - ---------- - proc_type : type - Class of the :obj:`Processor` allowed for this :obj:`Task`. - default : :obj:`Processor` or :obj:`types.FunctionType` or :obj:`types.MethodType` Default :obj:`Processor` to - use if no Processor is assigned. If a :obj:`types.FunctionType` or :obj:`types.MethodType`, an appropriate - :obj:`FunctionProcessor` will be created. - **kwargs : - Keyword arguments to overwrite on the supplied Processor. - - Raises - ------ - TypeError - If the default :obj:`Processor` is not of type `proc_type`. + def __init__(self, proc_type: type[Processor] = Processor, default: Processor | Callable[..., Any] = lambda data: data, **kwargs: Any) -> None: + """Initializes a new ``Task`` instance. + + Args: + proc_type (type[Processor], optional): The type of ``Processor`` allowed for this ``Task``. Defaults to ``Processor``. + default (Processor | Callable[..., Any], optional): The default processor for the ``Task``, which must either be a ``Processor`` or a + function. Defaults to the identity function. + **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., ``Slot``. + + Raises: + TypeError: The allowed ``Processor`` type for the ``Task``, ``proc_type``, is not of type ``Processor`` or a sub-class of ``Processor``. """ + if not issubclass(proc_type, Processor): - raise TypeError("Only sub-classes of Processors are allowed!") + raise TypeError('Only sub-classes of Processors are allowed.') if default is not None: default = ensure_processor(default, **kwargs) super().__init__(dtype=proc_type, default=default) - # pylint: disable=no-member - @Slot.default.setter - def default(self, value): + @property + def default(self) -> Processor | None: + """Gets or sets the default processor of the ``Task``. + + Note: + Actually, only the setter of the ``default`` property needs to be overridden. The proper way of overriding the setter of the ``default`` + property is to use the `@Slot.default.setter` idiom, but this, unfortunately, is detected as a false positive by MyPy. Although, they have + promised to support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used + very often in the wild. Their workaround solution is to also override the getter and then use the new getter to override the setter and + the deleter. I have tested it, only in the getter, `super().default` can be used, as it is not, yet, overridden, but in the setter and + deleter, the complete function must be re-implemented. Also, when the getter is overridden, not only the setter, but also the deleter must + be overridden, as otherwise, it will not be available anymore. + + Returns: + Processor | None: Returns the task's default processor. If not set, `None` is returned. + """ + + default_processor: Processor | None = super().default + return default_processor + + @default.setter + def default(self, value: Processor | Callable[..., Any] | None) -> None: + """Gets or sets the default processor of the ``Task``. Checks the new default processor is a ``Processor``. If not, it is converted to a + ``Processor`` using the ``ensure_processor`` function. The default processor is checked for consistency with the ``dtype``. + + Args: + value (Processor | Callable[..., Any] | None): The new default processor to set. If not set, `None` is returned. + + Raises: + TypeError: The default processor is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. + """ + if value is not None: value = ensure_processor(value) - # pylint: disable=no-member - Task.default.fset(self, value) + self._default = value - def __call__(self, obj=None, default=None): - return TaskPlug(self, obj=obj, default=default) + try: + self._consistent() + except TypeError as exception: + raise TypeError('The default processor is not consistent with the dtype.') from exception + @default.deleter + def default(self) -> None: + """Deletes the task's default processor.""" -class Pipeline(Processor): - """Abstract base class for all pipelines using MetaPipeline's tracked Task attributes + self._default = None - Attributes - ---------- - task_scheme : :obj:`collections.OrderedDict` - OrderedDict of Tasks which can be filled with :obj:`Processor`. - processes : :obj:`collections.OrderedDict` - OrderedDict of the :obj:`Processor` that filled the `task_scheme`. + def __call__(self, obj: Processor | Callable[..., Any] | None = None, default: Processor | Callable[..., Any] | None = None) -> TaskPlug: + """Creates a new corresponding ``TaskPlug`` container. - """ + Args: + obj (Processor | Callable[..., Any] | None, optional): A processor to initialize the newly created ``TaskPlug`` container's object value + to. Defaults to `None`. + default (Processor | Callable[..., Any] | None, optional): A processor to initialize the newly created ``TaskPlug`` container's default + value to. Defaults to `None`. - def checkpoint_processes(self): - """Find the checkpoint :obj:`Processor` closest to output and return an - :obj:`collections.OrderedDict` of that and all following :obj:`Processor`. + Returns: + TaskPlug: Returns the newly created ``TaskPlug`` container instance, obeying the type and optionality constraints. + """ - Returns - ------- - :obj:`collections.OrderedDict` - The :obj:`Processor` that is the checkpoint closest to output and all its following - :obj:`Processor` in an :obj:`OrderedDict`. + return TaskPlug(self, obj=obj, default=default) - Raises - ------ - RuntimeError - If there is not a single checkpoint. +class Pipeline(Processor): + """The abstract base class for all pipelines.""" + + def checkpoint_processes(self) -> OrderedDict[str, Processor]: + """Finds the ``Processor`` that is a checkpoint and is closest to the output. The final checkpoint processor and all following processors are + retrieved and returned in an ``OrderedDict``. + + Raises: + RuntimeError: No checkpoints were defined. + + Returns: + OrderedDict[str, Processor]: Returns an ``OrderedDict`` that contains the ``Processor`` that is closest to the output and a checkpoint, as + well as all following processors. The processors in the ``OrderedDict`` are ordered in the same way as they were in the pipeline, + i.e., from the checkpoint processor to the output processor. """ - checkpoint_process_list = [] - for key, proc in reversed(self.collect_attr(Task).items()): - checkpoint_process_list.append((key, proc)) - if proc.is_checkpoint: + + checkpoint_processor_list = [] + for key, processor in reversed(self.collect_attr(Task).items()): + checkpoint_processor_list.append((key, processor)) + if processor.is_checkpoint: break - if checkpoint_process_list and not checkpoint_process_list[-1][1].is_checkpoint: - raise RuntimeError("No checkpoint defined!") - checkpoint_processes = OrderedDict(checkpoint_process_list[::-1]) - return checkpoint_processes - def from_checkpoint(self): - """Re-evaluate from last check-pointed :obj:`Processor` using its respective output. + if checkpoint_processor_list and not checkpoint_processor_list[-1][1].is_checkpoint: + raise RuntimeError('No checkpoints were defined.') + + checkpoint_processors = OrderedDict(checkpoint_processor_list[::-1]) + return checkpoint_processors - Returns - ------- - object - Output of the whole pipeline, starting from check-pointed :obj:`Processor` closest to output. + def from_checkpoint(self) -> Any: + """Re-evaluates the pipeline from the last check-pointed ``Processor`` using the output from the checkpoint as input. - Raises - ------ - RuntimeError - If the check-pointed :obj:`Processor` closest to output does not have any - `checkpoint_data` store, i.e. the :obj:`Processor` was never called once after being declared a checkpoint. + Raises: + RuntimeError: If the check-pointed ``Processor`` closest to output does not have any ``checkpoint_data`` stored, i.e., the ``Processor`` + has not been called since being declared a checkpoint. + Returns: + Any: Returns the output of pipeline, starting from check-pointed ``Processor`` closest to output. """ + checkpoint_processes = self.checkpoint_processes() - data_iter = iter(checkpoint_processes.values()) - data = next(data_iter).checkpoint_data + data_iterator = iter(checkpoint_processes.values()) + data = next(data_iterator).checkpoint_data if data is None: - raise RuntimeError("No checkpoint data! Run whole pipeline first!") - for proc in data_iter: - data = proc(data) + raise RuntimeError('No checkpoint data found, the whole pipeline must be run first for a checkpoint to exist.') + for processor in data_iterator: + data = processor(data) return data - def function(self, data): + def function(self, data: Any) -> Any: """Propagate `data` through the whole pipeline from front to back, calling all Processors in series. - Attributes - ---------- - data : - The pipeline input passed to the first :obj:`Processor` in `self.processors`. Type depends on first - :obj:`Processor`. - - Returns - ------- - object - Output of all :obj:`Processor` that are flagged as pipeline outputs. If no processors - are flagged as outputs, return the output of the last processor. + Args: + data (Any): The input data to the pipeline. This is the input data for the first ``Processor`` in the pipeline. The type of the input data + depends on the first ``Processor`` in the pipeline. + Returns: + Any: Returns the output of the pipeline, which is the output of the of all processors in the pipeline that are flagged as pipeline + outputs. If no processors are flagged as outputs, the output of the last processor is returned. """ + outputs = [] - for proc in self.collect_attr(Task).values(): - data = proc(data) - if proc.is_output: + for processor in self.collect_attr(Task).values(): + data = processor(data) + if processor.is_output: outputs.append(data) if not outputs: return data @@ -174,18 +294,21 @@ def function(self, data): return outputs[0] return tuple(outputs) - def __repr__(self): - """Represent a Pipeline as str. - - Example - ------- - >>> MyPipeline() - MyPipeline( - FunctionProcessor(function=(lambda x: x.mean(1)),) -> output:np.ndarray - SciPyPDist(metric=sqeuclidean) -> output:np.ndarray - RadialBasisFunction(sigma=0.1) -> output:np.ndarray - MyProcess(stuff=3, func=Param(FunctionType, lambda x: x**2)) -> output:np.ndarray - ) + def __repr__(self) -> str: + """Generates a string representation of the pipeline, which contains all processors in the pipeline and their output types. + + Example: + >>> MyPipeline() + MyPipeline( + FunctionProcessor(processing_function=lambda x: x.mean(1),) -> output:numpy.ndarray + SciPyPDist(metric=sqeuclidean) -> output:numpy.ndarray + RadialBasisFunction(sigma=0.1) -> output:numpy.ndarray + MyProcess(stuff=3, func=Param(FunctionType, lambda x: x**2)) -> output:numpy.ndarray + ) + + Returns: + str: Returns a string representation of the pipeline, which contains all processors in the pipeline and their output types. """ - pipeline = '\n '.join([proc.__repr__() for proc in self.collect_attr(Task).values()]) + + pipeline = '\n '.join([processor.__repr__() for processor in self.collect_attr(Task).values()]) return f'{self.__class__.__name__}(\n {pipeline}\n)' diff --git a/source/corelay/pipeline/spectral.py b/source/corelay/pipeline/spectral.py index 5e3d424..ad62843 100644 --- a/source/corelay/pipeline/spectral.py +++ b/source/corelay/pipeline/spectral.py @@ -1,66 +1,76 @@ -"""SprAy-specific spectral clustering pipelines. +"""A module that contains SprAy-specific spectral clustering pipelines.""" -""" -import logging +from corelay.pipeline.base import Pipeline, Task +from corelay.processor.affinity import SparseKNN +from corelay.processor.clustering import KMeans +from corelay.processor.distance import SciPyPDist +from corelay.processor.embedding import EigenDecomposition +from corelay.processor.laplacian import SymmetricNormalLaplacian -from ..processor.distance import SciPyPDist -from ..processor.affinity import SparseKNN -from ..processor.laplacian import SymmetricNormalLaplacian -from ..processor.embedding import EigenDecomposition -from ..processor.clustering import KMeans -from .base import Pipeline, Task -LOGGER = logging.getLogger(__name__) +class SpectralEmbedding(Pipeline): + """A pipeline for spectral embeddings, which is customizable with different pre-processing, pairwise distance, affinity, laplacian, and embedding + functions. When an instance of the pipeline is called, it will return eigenvalues and eigenvectors of the spectral embedding, as NumPy arrays. -class SpectralEmbedding(Pipeline): - """Spectral Embedding with custom pipeline - - Parameters - ---------- - preprocessing : callable, optional - data pre-processing function of signature: (data : :obj:`numpy.ndarray`,) -> :obj:`numpy.ndarray` - pairwise_distance : callable, optional - pairwise distance function of signature: (data : :obj:`numpy.ndarray`,) -> :obj:`numpy.ndarray` - affinity : callable, optional - affinity function of signature: (distance : :obj:`numpy.ndarray`,) -> :obj:`numpy.ndarray` - laplacian : callable, optional - laplacian function of signature: (distance : :obj:`numpy.ndarray`,) -> :obj:`numpy.ndarray` - - Notes - ----- - Pre-computed distance matrices can be supplied by passing ``pairwise_distance=(lambda x: x)``. - Pre-computed affinity matrices can be supplied by additionally passing ``affinity=(lambda x: x)``. - Pre-computed graph laplacian matrices can be supplied by further passing ``laplacian=(lambda x: x)``. + Args: + preprocessing (Callable[[NDArray[Any]], Any], optional): A custom pre-processing function to be applied to the data before computing the + pairwise distance. Defaults to the identity function. + pairwise_distance (Callable[[NDArray[Any]], Any], optional): A custom pairwise distance function to be applied to the data. Defaults to the + euclidean distance. + affinity (Callable[[NDArray[Any]], Any], optional): A custom affinity function to be applied to the pairwise distance matrix. Defaults to a + sparse k-nearest neighbors graph with 10 neighbors. + laplacian (Callable[[NDArray[Any]], Any], optional): A custom graph laplacian function to be applied to the pairwise distance matrix. Defaults + to a symmetric normal laplacian. + embedding (Callable[[NDArray[Any]], Any], optional): A custom embedding function to be applied to the graph laplacian. Defaults to an + eigen decomposition with 32 eigenvalues. + Notes: + Pre-computed distance matrices can be supplied by passing `pairwise_distance=lambda x: x`. + Pre-computed affinity matrices can be supplied by additionally passing `affinity=lambda x: x`. + Pre-computed graph laplacian matrices can be supplied by further passing `laplacian=lambda x: x`. """ - preprocessing = Task(default=(lambda x: x)) + + preprocessing = Task(default=lambda x: x) + """A pre-processing task to be applied to the data before computing the pairwise distance task. Defaults to the identity function.""" + pairwise_distance = Task(default=SciPyPDist(metric='euclidean')) + """A pairwise distance task to be applied to the data. Defaults to the euclidean distance.""" + affinity = Task(default=SparseKNN(n_neighbors=10, symmetric=True)) + """An affinity task to be applied to the pairwise distance matrix. Defaults to a sparse k-nearest neighbors graph with 10 neighbors.""" + laplacian = Task(default=SymmetricNormalLaplacian()) + """A graph laplacian task to be applied to the pairwise distance matrix. Defaults to a symmetric normal laplacian.""" + embedding = Task(default=EigenDecomposition(n_eigval=32), is_output=True) + """An embedding task to be applied to the graph laplacian matrix. Defaults to an eigen decomposition with 32 eigenvalues.""" class SpectralClustering(SpectralEmbedding): - """Clustering on a spectral embedding - - Parameters - ---------- - clustering_fn : callable, optional - label-returning clustering function of signature: - (embedding : :obj:`numpy.ndarray`,) -> :obj:`numpy.ndarray` - **kwargs - Keyword arguments for `SpectralEmbedding` - - Returns - ------- - :obj:`numpy.ndarray` - Eigenvalues for spectral embedding - :obj:`numpy.ndarray` - Spectral embedding (eigenvectors) - :obj:`numpy.ndarray` - Labels of clustering on spectral embedding + """A pipeline for spectral clustering a spectral embedding, which is customizable with a custom clustering function. When an instance of the + pipeline is called, it will return eigenvalues and eigenvectors of the spectral embedding, and the labels of the spectral clustering, as NumPy + arrays. + Args: + select_eigenvector (Callable[[NDArray[Any]], Any], optional): A custom task to select the eigenvector from the spectral embedding. Defaults to + the second output of the spectral embedding. + clustering (Callable[[NDArray[Any]], Any], optional): A custom clustering function to be applied to the spectral embedding. Defaults to a + k-Means clustering with 2 clusters. + preprocessing (Callable[[NDArray[Any]], Any], optional): A custom pre-processing function to be applied to the data before computing the + pairwise distance. Defaults to the identity function. + pairwise_distance (Callable[[NDArray[Any]], Any], optional): A custom pairwise distance function to be applied to the data. Defaults to the + euclidean distance. + affinity (Callable[[NDArray[Any]], Any], optional): A custom affinity function to be applied to the pairwise distance matrix. Defaults to a + sparse k-nearest neighbors graph with 10 neighbors. + laplacian (Callable[[NDArray[Any]], Any], optional): A custom graph laplacian function to be applied to the pairwise distance matrix. Defaults + to a symmetric normal laplacian. + embedding (Callable[[NDArray[Any]], Any], optional): A custom embedding function to be applied to the graph laplacian. Defaults to an + eigen decomposition with 32 eigenvalues. """ + select_eigenvector = Task(default=lambda x: x[1]) + """A task to select the eigenvector from the spectral embedding. Defaults to the second output of the spectral embedding.""" + clustering = Task(default=KMeans(n_clusters=2), is_output=True) + """A clustering task to be applied to the spectral embedding. Defaults to a k-Means clustering with 2 clusters.""" diff --git a/source/corelay/plugboard.py b/source/corelay/plugboard.py index 22b56c0..cdbc5f2 100644 --- a/source/corelay/plugboard.py +++ b/source/corelay/plugboard.py @@ -1,525 +1,674 @@ -"""Plugboards are classes which contain Slots that are filled using Plugs.""" +"""A module that contains plugboards, which are classes that contains slots filled using plugs.""" + +from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, LambdaType, MethodType +from typing import Any import numpy -from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, MethodType -from .tracker import Tracker +from corelay.tracker import Tracker class EmptyInit: - """Empty Init is a class intended to be inherited as a last step down the MRO, to catch any remaining positional - and/or keyword arguments and thus raise proper Exceptions. + """Empty Init is a class intended to be inherited as a last step down the MRO, to catch any remaining positional and/or keyword arguments and thus + raise proper Exceptions. """ - # Following is not useless, since this super delegation causes python to raise a more informative exception. - # pylint: disable=useless-super-delegation - def __init__(self): + + def __init__(self) -> None: + """Initializes a new instance of EmptyInit. + + Note: + This is not intended to be called directly, but rather by the constructor of the next class up in the inheritance hierarchy. The + super-delegation is not unnecessary, even if PyLint claims so, since this is causes Python to raise a more informative exception when the + user tries to pass more keyword arguments than are accepted by the constructors in the inheritance hierarchy. If the constructor of the + penultimate class in inheritance hierarchy calls this constructor and there are still keyword arguments left, Python will raise a + TypeError with a message like: "TypeError: object.__init__() takes exactly one argument (the instance to initialize)". This may confuse + users, but if the constructor call is delegated to the next class up (i.e., object), the exception will be more informative and say + something like: "TypeError: Empty.__init__() takes 0 positional arguments but 1 was given". + """ + + # pylint: disable=useless-super-delegation super().__init__() class Slot(EmptyInit): - """Slots are descriptors that contain objects in a container called `Plug`. `Slot`s have a dtype and a default - value, which are enforced to be consistent. When a Slot instance is accessed in a class, it will return the - contained object of its `Plug` container. When accessing or assigning Slot instances in a class, if it never has - been accessed before, a Plug object is stored in the class' __dict__ under the same name the Slot was assigned to in - the class. Slots may have their default value set to None, in which case setting Plugs belonging to it must have - either a default, or an explicit object value on their own. Calling a Slot instance creates a corresponding Plug - container instance. - - Note - ---- - See `https://docs.python.org/3/howto/descriptor.html` for information on descriptors. - - Attributes - ---------- - dtype : type or tuple of type - Allowed type(s) of the parameter. - default : :obj:`dtype` - Default parameter value, should be an instance of (one of) :obj:`dtype`. - optional : bool - Non-mutable. True, if Slot has a non-None default value, e.g. is optional. - - See Also - -------- - :obj:`Plugboard` - :obj:`Plug` - + """Slots are descriptors that contain objects in a container called ``Plug``. Instances of the ``Slot`` class have a ``dtype`` and a ``default`` + value, which are enforced to be consistent. When a ``Slot`` instance is accessed in a class, it will return the contained object of its ``Plug`` + container. When accessing or assigning ``Slot`` instances in a class that have never been accessed before, a ``Plug`` object is stored in the + class' ``__dict__`` under the same name the ``Slot`` was assigned to in the class. Slots may have their default value set to `None`, in which case + setting Plugs belonging to it must have either a default value, or an explicit ``obj`` value on their own. Calling a ``Slot`` instance creates a + corresponding ``Plug`` container instance. + + Note: + See `https://docs.python.org/3/howto/descriptor.html` for more information on descriptors. + + See Also: + :obj:`Plugboard` + :obj:`Plug` """ - def __init__(self, dtype=object, default=None, **kwargs): - """Configure type and default value of slot. A consistency check is performed. - - Parameters - ---------- - dtype : type or tuple of type - Allowed type(s) inside the slot. - default : :obj:`dtype` - Default plug value, should be an instance of (one of) :obj:`dtype`. May be `None` to indicate that no - default value has been set. - **kwargs : - Keyword arguments passed down to potential entries below Slot in the MRO, for cooperativity's sake. In - normal cases, this next class will be `EmptyInit`, and thus accept no more kwargs. + def __init__( + self, + dtype: type | tuple[type, ...] = object, + default: Any = None, + **kwargs: Any + ) -> None: + """Initializes a new instance of ``Slot``. Configures that data type and the default value of the slot. A consistency check is performed to + ensure that the default value is of the correct type. + + Args: + dtype (type | tuple[type, ...], optional): The data type of the slot. This can be a single type or a tuple of types. The value of the + plug, as well as the default value, must be of this type or one of the types in the tuple. Defaults to ``object``. + default (Any, optional): The default value of the plug. The default value must be an instance of the specified data type ``dtype`` or one + of the types in the tuple. If no default value is set, it will be `None`. When a plug is created without an explicit value, it will + use this default value. Defaults to `None`. + **kwargs (Any): Additional keyword arguments that are passed to the parent class constructor. This is done for cooperativity's sake, as + the next class one step up in the inheritance hierarchy will be `EmptyInit`, which does not accept any additional keyword arguments + and will raise an exception if any are passed. """ + super().__init__(**kwargs) - self._dtype = dtype if isinstance(dtype, tuple) else (dtype,) + + self._dtype: tuple[type, ...] = dtype if isinstance(dtype, tuple) else (dtype,) self._default = default self.__name__ = '' + self._consistent() _function_types = ( + LambdaType, + MethodType, BuiltinFunctionType, BuiltinMethodType, - MethodType, numpy.ufunc, type(numpy.max) ) + """Contains all types that may represent a function. This is necessary, since many functions are not of type ``FunctionType``, e.g., lambda + expressions, methods, built-in functions, built-in methods, and NumPy universal functions. Also, since NumPy 1.26, NumPy array functions are no + longer actual functions; so, for example, something like `pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)]` would not + work anymore. When the user sets the dtype to ``FunctionType`` or ``FunctionType`` is an element of the tuple, then the types in this tuple are + added to the data types that ``_consistent`` checks against. This way, the user does not have to worry about the fact that many functions are not + of type ``FunctionType``. + """ - def _consistent(self): - """Check whether self.dtype and self.default are consistent, e.g. self.default is either None, or of type - self.dtype. + def _consistent(self) -> None: + """Checks whether ``dtype`` and ``default`` are consistent, i.e., ``default`` is either `None`, of type ``dtype``, or one of the types in the + tuple ``dtype``. - Raises - ------- - TypeError - If self is not consistent in the sense written above. + Raises: + TypeError: The ``default`` value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. """ + if ( - not isinstance(self.dtype, type) - and not ( - isinstance(self.dtype, tuple) - and all(isinstance(element, type) for element in self.dtype) - ) + not isinstance(self.dtype, type) and + not ( + isinstance(self.dtype, tuple) and + all(isinstance(element, type) for element in self.dtype) + ) ): raise TypeError( - f"'{type(self).__name__}' object '{self.__name__}' default values '{self.dtype}' " - "is neither a type, nor a tuple of types." + f'The data type of the "{type(self).__name__}" object "{self.__name__}" is neither a type, nor a tuple of types, but "{self.dtype}".' ) # If the user sets the dtype to FunctionType, then we should add some more types to check against, because many functions are not of type - # FunctionType, e.g., builtin functions, methods, and, since NumPy 1.26, NumPy array functions are no longer actual functions; so, for - # example, something like pooling_function = Param(FunctionType, np.sum) would not work anymore + # FunctionType, e.g., expressions, methods, built-in functions, built-in methods, NumPy universal functions, and NumPy array functions effective_dtypes = self.dtype if isinstance(self.dtype, tuple) else (self.dtype,) if FunctionType in effective_dtypes: effective_dtypes = effective_dtypes + Slot._function_types if self.default is not None and not isinstance(self.default, effective_dtypes): raise TypeError( - f"'{type(self).__name__}' object '{self.__name__}' default value is not of type '{self.dtype}'." + f'The data type of the default value of the "{type(self).__name__}" object "{self.__name__}" is not of type "{self.dtype}".' ) - def get_plug(self, instance, obj=None, default=None): - """Get a corresponding Plug be accessing the Slot's instance's __dict__. For accessing a newly created and - possibly mandatory Plug's, a obj and default value may be passed. - - Parameters - ---------- - instance : object - An instance of the class Slot was assigned in. - obj : :obj:`dtype` - Object value to write to newly created Plug instances. - default : :obj:`dtype` - Default value to write to newly created Plug instances. - - Returns - ------- - :obj:`Plug` - Either an existing Plug container from the instance's __dict__ if available, or a newly created Plug - container instance, which is also appended to the instance's __dict__. + def get_plug(self, instance: Any, obj: Any = None, default: Any = None) -> 'Plug': + """Gets a corresponding ``Plug`` that can be used to access the ``__dict__`` of the ``Slot`` instance . In case a new ``Plug`` has to be + created, a ``obj`` and ``default`` value may be specified. + + Args: + instance (Any): An instance of the class the ``Slot`` was assigned in. + obj (Any): The object value to write to newly created ``Plug`` instances. + default (Any): The default value to write to newly created ``Plug`` instances. + Returns: + Plug: Returns a ``Plug`` container. If a ``Plug`` instance already exists in the instance's ``__dict__`` it is returned. Otherwise, a new + ``Plug`` container is created, which is also appended to the instance's ``__dict__``. If a new ``Plug`` instance is created, the ``obj`` + and ``default`` values are set to the values passed in. """ + try: - plug = instance.__dict__[self.__name__] + plug: Plug = instance.__dict__[self.__name__] except KeyError: plug = self(obj=obj, default=default) instance.__dict__[self.__name__] = plug return plug - def __set_name__(self, owner, name): - """Set the name of the Slot when assigned under a class. Necessary to write the correct __dict__ entry of the - parent class. - - Parameters - ---------- - owner : type - Parent class the Slot was defined in. - name : str - Name under which the Slot was defined in the parent class. + def __set_name__(self, owner: type, name: str) -> None: # pylint: disable=unused-argument + """Is invoked, when the ``Slot`` is assigned to a class or instance attribute. Sets the name of the slot when assigned under a class. + Necessary to write the correct ``__dict__`` entry in the parent class. + Args: + owner (type): The parent class the ``Slot`` was assigned in. + name (str): The name under which the ``Slot`` was assigned in the parent class. """ - self.__name__ = name - def __get__(self, instance, owner): - """If accessed under the class, return the Slot object. If accessed under an instance, return the corresponding - Plug container's value. + self.__name__ = name - Parameters - ---------- - instance : type - Instance of parent class the Slot was defined in. - owner : type - Parent class the Slot was defined in. + def __get__(self, instance: Any, owner: type) -> 'Slot | Any': # pylint: disable=unused-argument + """Is invoked, when the class or instance attribute the ``Slot`` was assigned to is read. When the ``Slot`` is accessed from a class, the + ``Slot`` instance itself is returned. If accessed using an instance, the corresponding ``Plug`` container's value is returned. - Returns - ------- - object - Value of the Plug container + Args: + instance (Any): The instance of the parent class the ``Slot`` was assigned in. + owner (type): The parent class the ``Slot`` was defined in. + Returns: + Slot | Any: Returns the value of the Plug container. """ - if instance is None: - return self - return self.get_plug(instance).obj - def __set__(self, instance, value): - """Set the instance's Plug container object value + return self if instance is None else self.get_plug(instance).obj - Parameters - ---------- - instance : type - Instance of parent class the Slot was defined in. - value : object - Value to set the Plug container's object value to + def __set__(self, instance: Any, value: Any) -> None: + """Is invoked, when the class or instance attribute the ``Slot`` was assigned to is written. Sets the instance's ``Plug`` container object + value. + Args: + instance (Any): The instance of the parent class the ``Slot`` was assigned in. + value (Any): The value to set the Plug container's object value to. """ - self.get_plug(instance, obj=value).obj = value - def __delete__(self, instance): - """Delete the instance's Plug container object value if existing, enforcing the use of its default value. - - Parameters - ---------- - instance : type - Instance of parent class the Slot was defined in. + self.get_plug(instance, obj=value).obj = value - Raises - ------ - TypeError - If Slot is mandatory and Plug does not have a default value + def __delete__(self, instance: Any) -> None: + """Is invoked, when the class or instance attribute the ``Slot`` was assigned to is deleted. Deletes the instance's ``Plug`` container object + value if it exists, enforcing the use of its default value. + Args: + instance (Any): The instance of the parent class the ``Slot`` was assigned in. """ + del self.get_plug(instance).obj @property - def default(self): - """Get the Slot's default value""" + def default(self) -> Any: + """Gets or sets the default value of the ``Slot``. + + Returns: + Any: Returns the slot's default value. If not set, `None` is returned. + """ + return self._default @default.setter - def default(self, value): - """Set the Slot's default value. Checks for consistency.""" + def default(self, value: Any) -> None: + """Gets or sets the default value of the ``Slot``. Checks the new default value for consistency with the ``dtype``. + + Args: + value (Any): The new default value to set. If not set, `None` is returned. + + Raises: + TypeError: The default value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. + """ + + original_default_value = self._default self._default = value - self._consistent() + + try: + self._consistent() + except TypeError as exception: + self._default = original_default_value + raise TypeError( + f'The data type of the default value of the "{type(self).__name__}" object "{self.__name__}" is not of type "{self.dtype}".' + ) from exception @default.deleter - def default(self): - """Set the Slot's default value.""" + def default(self) -> None: + """Deletes the default value of the ``Slot``.""" + self._default = None @property - def dtype(self): - """Get the Slot's dtype.""" + def dtype(self) -> type | tuple[type, ...]: + """Gets or sets the slot's ``dtype``. + + Returns: + type | tuple[type, ...]: Returns the slot's ``dtype``. If not set, `None` is returned. + """ + return self._dtype @dtype.setter - def dtype(self, value): - """Set the Slot's dtype. Checks for consistency.""" - self._dtype = value - self._consistent() + def dtype(self, value: type | tuple[type, ...]) -> None: + """Gets or sets the slot's ``dtype``. Checks the default value of the slot for consistency with the new ``dtype``. + + Args: + value (type | tuple[type, ...]): The new ``dtype`` to set. If not set, `None` is returned. + + Raises: + TypeError: The default value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. + """ + + original_dtype_value = self._dtype + self._dtype = value if isinstance(value, tuple) else (value,) + + try: + self._consistent() + except TypeError as exception: + self._dtype = original_dtype_value + raise TypeError( + f'The data type of the dtype value of the "{type(self).__name__}" object "{self.__name__}" is not of type "{self.dtype}".' + ) from exception @property - def optional(self): - """Get whether the Slot is optional.""" + def optional(self) -> bool: + """Gets a value indicating whether the ``Slot`` is optional. + + Returns: + bool: Returns `True` if the ``Slot`` is optional, i.e., it has a default value, and `False` otherwise. + """ + return self.default is not None - def __call__(self, obj=None, default=None): + def __call__(self, obj: Any = None, default: Any = None) -> 'Plug': """Create a new corresponding Plug container - Parameters - ---------- - obj : object - Value to initialize the newly created Plug container's object value to. - default : object - Value to initialize the newly created Plug container's default value to. - - Returns - ------- - :obj:`Plug` - A newly created corresponding Plug container instance, obeying the type and optionality constraints. + Args: + obj (Any): A value to initialize the newly created ``Plug`` container's object value to. Defaults to `None`. + default (Any): A value to initialize the newly created ``Plug`` container's default value to. Defaults to `None`. + Returns: + Plug: Returns a newly created ``Plug`` container instance, obeying the type and optionality constraints. """ + return Plug(self, obj=obj, default=default) class Plug(EmptyInit): - """Container class to fill Slots associated with a certain instance. The instance is usually of type Plugboard, but - may be of any kind of type. - - Attributes - ---------- - obj : object - Explicitly defined object held in the Plug container. If not set, self.default is returned. - default : object - Plug-dependent lower-priority object held in the container. If not set, self.fallback is returned. - fallback : object - Slot-dependent lowest-priority value to fall back to when no value has been assigned to the container. If not - set, a TypeError is returned. - slot : :obj:`Slot` - Slot with which this Plug is associated. - dtype : type - dtype of the associated Slot. - optional : bool - Whether default or fallback is not None. - - See Also - -------- - :obj:`Slot` - :obj:`Plugboard` + """Container class to fill slots associated with a certain instance. The instance is usually of type ``Plugboard``, but may be of any kind of + type. + See Also: + :obj:`Slot` + :obj:`Plugboard` """ - def __init__(self, slot, obj=None, default=None, **kwargs): - """Initialize container and check for consistency. - - Parameters - ---------- - slot : :obj:`Slot` - Slot with which this Plug is associated. - obj : object - Explicitly defined object held in the Plug container. If not set, self.default is returned. - default : object - Plug-dependent lower-priority object held in the container. If not set, self.fallback is returned. - **kwargs : - Keyword arguments passed down to potential entries below Plug in the MRO, for cooperativity's sake. In - normal cases, this next class will be `EmptyInit`, and thus accept no more kwargs. + def __init__(self, slot: Slot, obj: Any = None, default: Any = None, **kwargs: Any) -> None: + """Initializes a new ``Plug`` instance and checks for consistency. + + Args: + slot (Slot): The ``Slot`` instance to associate with this ``Plug``. + obj (Any): An explicitly defined object held in the ``Plug`` container. If not set, ``default`` is returned as its value. + default (Any): A plug-dependent lower-priority object held in the ``Plug`` container. If not set, ``fallback`` is returned. + **kwargs (Any): Keyword arguments passed down to the base class constructor, for cooperativity's sake. In normal cases, this next class + will be `EmptyInit`, which accepts no more keyword arguments and will raise an exception. """ + super().__init__(**kwargs) + self._obj = obj self._slot = slot self._default = default + self._consistent() _function_types = ( + LambdaType, + MethodType, BuiltinFunctionType, BuiltinMethodType, - MethodType, numpy.ufunc, type(numpy.max) ) + """Contains all types that may represent a function. This is necessary, since many functions are not of type ``FunctionType``, e.g., lambda + expressions, methods, built-in functions, built-in methods, and NumPy universal functions. Also, since NumPy 1.26, NumPy array functions are no + longer actual functions; so, for example, something like `pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)]` would not + work anymore. When the user sets the dtype to ``FunctionType`` or ``FunctionType`` is an element of the tuple, then the types in this tuple are + added to the data types that ``_consistent`` checks against. This way, the user does not have to worry about the fact that many functions are not + of type ``FunctionType``. + """ - def _consistent(self): - """Checks whether all values are consistent, e.g. at least one of obj, default and fallback is set and of dtype - slot.dtype. - - Raises - ------ - TypeError - If none of obj, default and fallback is set, or obj is not of type slot.dtype. + def _consistent(self) -> None: + """Checks whether all values are consistent, i.e., at least one of ``obj``, ``default``, or ``fallback`` is set and of the data type specified + in `slot.dtype`, or one of the types in the tuple `slot.dtype`. + Raises: + TypeError: None of ``obj``, ``default``, or ``fallback`` is set, or the value is not consistent with the `slot.dtype` or one of the types + in the tuple `slot.dtype`. """ + if self.obj is None: - raise TypeError( - f"'{type(self.slot).__name__}' object '{self.slot.__name__}' is mandatory, " - "yet it has been accessed without being set." - ) + raise TypeError(f'"{type(self.slot).__name__}" object "{self.slot.__name__}" is mandatory, yet it has been accessed without being set.') # If the user sets the dtype to FunctionType, then we should add some more types to check against, because many functions are not of type - # FunctionType, e.g., builtin functions, methods, and, since NumPy 1.26, NumPy array functions are no longer actual functions; so, for - # example, something like pooling_function = Param(FunctionType, np.sum) would not work anymore + # FunctionType, e.g., expressions, methods, built-in functions, built-in methods, NumPy universal functions, and NumPy array functions effective_dtypes = self.slot.dtype if isinstance(self.slot.dtype, tuple) else (self.slot.dtype,) if FunctionType in effective_dtypes: effective_dtypes = effective_dtypes + Plug._function_types if not isinstance(self.obj, effective_dtypes): - raise TypeError( - f"'{type(self.slot).__name__}' object '{self.slot.__name__}' value '{self.obj}' " - f"is not of type '{self.slot.dtype}'." - ) + raise TypeError(f'"{type(self.slot).__name__}" object "{self.slot.__name__}" value "{self.obj}" is not of type "{self.slot.dtype}".') @property - def slot(self): - """Get associated Slot.""" + def slot(self) -> Slot: + """Gets or sets the associated ``Slot``. + + Returns: + Slot: Returns the associated ``Slot``. If not set, `None` is returned. + """ + return self._slot @slot.setter - def slot(self, value): - """Set associated Slot. Checks for consistency""" + def slot(self, value: Slot) -> None: + """Gets or sets associated ``Slot`` and checks for consistency. + + Args: + value (Slot): The new ``Slot`` to set. + """ + self._slot = value self._consistent() @property - def dtype(self): - """Get associated Slot's dtype. Non-mutable.""" - return self.slot.dtype + def dtype(self) -> type | tuple[type, ...]: + """Gets the ``dtype`` of the associated ``Slot``. The ``dtype`` property is non-mutable. + + Returns: + type | tuple[type, ...]: Returns the ``dtype`` of the associated ``Slot``. + """ + + return self.slot.dtype[0] if isinstance(self.slot.dtype, tuple) and len(self.slot.dtype) == 1 else self.slot.dtype @property - def optional(self): - """Get whether container has default values. Non-mutable.""" + def optional(self) -> bool: + """Gets a value indicating whether the ``Plug`` container has a default value. The ``optional`` property is non-mutable. + + Returns: + bool: Returns `True` if the ``Plug`` container has a default value, and `False` otherwise. + """ + return self.default is not None @property - def fallback(self): - """Get associated Slot's default value. Non-mutable.""" + def fallback(self) -> Any: + """Gets the default value of the associated ``Slot``. The ``fallback`` property is non-mutable. + + Returns: + Any: Returns the default value of the associated ``Slot``. + """ + return self.slot.default @property - def obj(self): - """Get contained object value. Get self.default instead if not set.""" + def obj(self) -> Any: + """Gets or sets the value of the object contained in the ``Plug``. If the ``Plug`` does not contain an object value, ``default`` is retrieved + instead. + + Returns: + Any: Returns the object value contained in the ``Plug``. If not set, ``default`` is returned. + """ + if self._obj is None: return self.default return self._obj @obj.setter - def obj(self, value): - """Set contained object value. Checks for consistency.""" + def obj(self, value: Any) -> None: + """Gets or sets the value of the object contained in the ``Plug`` and checks for consistency. + + Args: + value (Any): The new object value to set. + + Raises: + TypeError: The object value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. + """ + + original_obj_value = self._obj self._obj = value - self._consistent() + + try: + self._consistent() + except TypeError as exception: + self._obj = original_obj_value + raise TypeError(f'The data type of the obj value of the "{type(self).__name__}" object is not of type "{self.dtype}".') from exception @obj.deleter - def obj(self): - """Delete contained object value by setting it to None.""" + def obj(self) -> None: + """Deletes the value of the object contained in the ``Plug`` by setting it to `None`.""" + self.obj = None @property - def default(self): - """Get low-priority contained default value. Get self.fallback instead if not set.""" + def default(self) -> Any: + """Gets or sets the default value of the ``Plug``. If the ``default`` value is not set, then the ``fallback`` value is retrieved instead. + + Returns: + Any: Returns the default value of the ``Plug``. If not set, ``fallback`` is returned. + """ + if self._default is None: return self.fallback return self._default @default.setter - def default(self, value): - """Set low-priority contained default value. Checks for consistency.""" + def default(self, value: Any) -> None: + """Gets or sets the default value of the ``Plug`` and checks for consistency. + + Args: + value (Any): The new default value to set. + + Raises: + TypeError: The default value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the + types in the tuple ``dtype``. + """ + + original_default_value = self._default self._default = value - self._consistent() + + try: + self._consistent() + except TypeError as exception: + self._default = original_default_value + raise TypeError(f'The data type of the default value of the "{type(self).__name__}" object is not of type "{self.dtype}".') from exception @default.deleter - def default(self): - """Delete low-priority contained default value by setting it to None.""" + def default(self) -> None: + """Deletes the default value of the ``Plug`` by setting it to `None`.""" + self.default = None class SlotDefaultAccess: - """A proxy-object descriptor class to access Plug default values of the owning class, since Plug-instances can not - be returned except by accessing a classes' __dict__. - - See Also - -------- - :obj:`Slot` - :obj:`Plugboard` - :obj:`Plug` + """A proxy-object descriptor class to access the default values of the owning class of a ``Slot``, since ``Slot`` instances cannot be returned + except by accessing a classes' ``__dict__``. + See Also: + :obj:`Slot` + :obj:`Plugboard` + :obj:`Plug` """ - def __init__(self, instance=None): - """Initialize associated instance reference. - Parameters - ---------- - instance : object - Instance of the associated, Slot-owning owner class. + def __init__(self, instance: Tracker | Any = None) -> None: + """Initializes a new ``SlotDefaultAccess`` instance. + Args: + instance (Tracker | Any): The instance of the class the ``SlotDefaultAccess`` is associated with. """ - object.__setattr__(self, '_instance', instance) - def _get_plug(self, name, default=None): - """Return Plug of the instance of the associated Slot-owning owner class by name, by calling the Slot's - `get_plug` method. + # We cannot just assign self._instance here, because this would cause __get__ to be called, which in turn would create a new instance of + # SlotDefaultAccess, which would call __init__ again, which would call __get__ again, and so on, resulting in an infinite recursion; this is + # circumvented by using object.__setattr__ instead, which does not call __get__ + self._instance: Tracker | Any + object.__setattr__(self, '_instance', instance) - Parameters - ---------- - name : str - Name of the Slot. - default : object - Default value to set if there does not yet exist a Plug instance associated with the Slot and instance. + def _get_plug(self, name: str, default: Any = None) -> Plug: + """Gets the ``Plug`` of the instance of the associated ``Slot``-owning class by name, by calling the ``get_plug`` method of the ``Slot``. - Returns - ------- - :obj:`Plug` - Plug container associated with the instance of the Slot-owning owner class and name. + Args: + name (str): The name of the ``Slot``. + default (Any): The default value to set if the ``Plug`` associated with the ``Slot`` does not exist yet. - Raises - ------ - AttributeError - If there is no attribute in the associated owner class of this name of type :obj:`Slot`. + Raises: + AttributeError: There is no attribute in the associated owner class of this name of type ``Slot``. + Returns: + Plug: Returns the ``Plug`` container associated with the instance of the ``Slot``-owning class and name. """ + + if self._instance is None: + raise AttributeError('The instance of the class the SlotDefaultAccess is associated with is not set.') slot = getattr(type(self._instance), name) if not isinstance(slot, Slot): - raise AttributeError( - f"'{type(self._instance)}' object has no attribute '{name}' of type '{Slot}'" - ) + raise AttributeError(f'"{type(self._instance)}" object has no attribute "{name}" of type "{Slot}", it is of type "{type(slot)}".') return slot.get_plug(self._instance, default=default) - def __get__(self, instance, owner): - """Return a new instance of SlotDefaultAccess, initialized with the provided instance value.""" + def __get__(self, instance: Any, owner: Any) -> 'SlotDefaultAccess': # pylint: disable=unused-argument + """Gets a new instance of ``SlotDefaultAccess``, initialized with the provided instance value. + + Args: + instance (Any): The instance of the class the ``SlotDefaultAccess`` is associated with. + owner (Any): The owner class of the ``SlotDefaultAccess``. + + Returns: + SlotDefaultAccess: Returns a new instance of ``SlotDefaultAccess`` initialized with the provided instance value. + """ + return type(self)(instance) - def __set__(self, instance, value): - """Set the default values of the associated owner class instance's Slots by assigning a dict.""" - new = type(self)(instance) + def __set__(self, instance: Any, value: dict[str, Any]) -> None: + """Sets the default values of the associated owner class instance's slots by assigning a the values of the dictionary specified + in ``value``. + + Args: + instance (Any): The instance of the class the ``SlotDefaultAccess`` is associated with. + value (dict[str, Any]): A dictionary containing the default values to set for the associated owner class instance's slots. + + Raises: + TypeError: The ``value`` is not a dictionary. + """ + if not isinstance(value, dict): - raise TypeError("Can only directly set default values using a dict!") - for key, val in value.items(): - setattr(new, key, val) + raise TypeError('Can only directly set default values using a dict.') - def __getattr__(self, name): - """Get the default values of the associated owner class instance's Slots 'name' Plug by attribute.""" - return self._get_plug(name).default + slot_default_accessor = type(self)(instance) + for attribute_name, attribute_default_value in value.items(): + setattr(slot_default_accessor, attribute_name, attribute_default_value) - def __setattr__(self, name, value): - """Set the default values of the associated owner class instance's Slot's 'name' Plug by attribute.""" - self._get_plug(name, default=value).default = value + def __getattr__(self, name: str) -> Any: + """Gets the default value of the of the slot with the specified ``name`` that associated with the owner class instance. - def __delattr__(self, name): - """Delete the default values of the associated owner class instance's Slot 'name' Plug by attribute.""" - del self._get_plug(name).default + Args: + name (str): The name of the slot to get the default value for. - def __dir__(self): - """Return a list of all Slots of the owner class.""" - return list(type(self._instance).collect(Slot)) + Raises: + AttributeError: There is no slot with the specified ``name`` in the associated owner class instance. + Returns: + Any: Returns the default value of the slot with the specified ``name`` that associated with the owner class instance. + """ -class Plugboard(Tracker, EmptyInit): - """Optional Manager class for Slots. Uses SlotDefaultAccess to access Plug default values. Also initializes Plug - container object values during instantiation by keywords. + try: + return self._get_plug(name).default + except AttributeError as exception: + raise AttributeError(f'"{type(self._instance)}" object has no attribute "{name}" of type "{Slot}".') from exception + + def __setattr__(self, name: str, value: Any) -> None: + """Sets the default value of the slot with the specified ``name`` that associated with the owner class instance. + + Args: + name (str): The name of the slot to set the default value for. + value (Any): The new default value to set. + + Raises: + AttributeError: There is no slot with the specified ``name`` in the associated owner class instance. + TypeError: The default value is not consistent with the ``dtype`` of the ``Plug``, i.e., it is neither `None`, nor of the type ``dtype`` + or one of the types in the tuple ``dtype``. + """ - Parameters - ---------- - default : :obj:`SlotDefaultAccess` - Proxy object to access instance's Plug's default values by attribute. + try: + self._get_plug(name, default=value).default = value + except AttributeError as exception: + raise AttributeError(f'"{type(self._instance)}" object has no attribute "{name}" of type "{Slot}".') from exception + except TypeError as exception: + slot: Slot | None = getattr(type(self._instance), name, None) + if slot is not None: + raise TypeError( + f'The data type of the default value of the "{type(self._instance)}" object is not of type "{slot.dtype}".' + ) from exception + raise TypeError(f'The data type of the default value of the "{type(self._instance)}" object is invalid.') from exception + + def __delattr__(self, name: str) -> None: + """Deletes the default value of the slot with the specified ``name`` that associated with the owner class instance. + + Args: + name (str): The name of the slot to delete the default value for. + + Raises: + AttributeError: There is no slot with the specified ``name`` in the associated owner class instance. + """ + + try: + del self._get_plug(name).default + except AttributeError as exception: + raise AttributeError(f'"{type(self._instance)}" object has no attribute "{name}" of type "{Slot}".') from exception + + def __dir__(self) -> list[str]: + """Returns a list of all slots of the associated owner class instance. + + Returns: + list[str]: Returns a list of all slots of the associated owner class instance. + """ + + return list(type(self._instance).collect(Slot)) - See Also - -------- - :obj:`Slot` - :obj:`SlotDefaultAccess` - :obj:`Plug` +class Plugboard(Tracker, EmptyInit): + """Optional Manager class for slots. Uses ``SlotDefaultAccess`` to access ``Plug`` default values. Also initializes ``Plug`` container object + values during instantiation by keywords. + + See Also: + :obj:`Slot` + :obj:`SlotDefaultAccess` + :obj:`Plug` """ - default = SlotDefaultAccess() - def __init__(self, **kwargs): - """Initialize Plug container object values by keyword arguments. + default = SlotDefaultAccess() + """Contains a proxy object to access the default values of the owning class of a ``Plug``.""" - Parameters - ---------- - **kwargs : - Keyword arguments to initialize Slots. Only keyword arguments which correspond to class' Slot attribute - names are processed. All other keyword arguments are passed to the next class' __init__ method in the MRO. + def __init__(self, **kwargs: Any) -> None: + """Initializes a new ``Plugboard`` instance and initializes the slots via the keyword arguments passed in. + Args: + **kwargs (Any): The keyword arguments that are used to initialize slots. Only keyword arguments which correspond to the slot attribute + names of the class are processed. All other keyword arguments are passed to the constructor of the next class in the inheritance + hierarchy. """ + slots = self.collect(Slot) - non_slot_kwargs = {key: val for key, val in kwargs.items() if key not in slots} - # also pass responsibility to raise Exception on wrong kwargs to super-class + non_slot_kwargs = {attribute_name: attribute_value for attribute_name, attribute_value in kwargs.items() if attribute_name not in slots} super().__init__(**non_slot_kwargs) - for key, val in kwargs.items(): - setattr(self, key, val) - def reset_defaults(self): - """Delete default values of all Plugs of this instance.""" - for key in self.collect(Slot): - delattr(self.default, key) + for attribute_name, attribute_value in kwargs.items(): + setattr(self, attribute_name, attribute_value) + + def reset_defaults(self) -> None: + """Deletes the default values of all plugs of this instance.""" - def update_defaults(self, **kwargs): - """Update default values of Plugs of this instance by providing their name and a new value as keyword - arguments. + # Please note, that although, self.default has not been initialized with an instance of the owning class, delattr will cause __get__ to be + # called on self.default, which will return a new instance of SlotDefaultAccess, which is initialized with the instance of the owning class; + # so at runtime, this will magically work, even though it looks like it should not + for attribute_name in self.collect(Slot): + delattr(self.default, attribute_name) + def update_defaults(self, **kwargs: Any) -> None: + """Updates the default values of all plugs of this instance using the keyword arguments. + + Args: + **kwargs (Any): The keyword arguments that are used to update the default values of the plugs. """ - for key, val in kwargs.items(): - setattr(self.default, key, val) + + for attribute_name, new_attribute_default_value in kwargs.items(): + setattr(self.default, attribute_name, new_attribute_default_value) diff --git a/source/corelay/processor/__init__.py b/source/corelay/processor/__init__.py index 065d2f1..a045e0c 100644 --- a/source/corelay/processor/__init__.py +++ b/source/corelay/processor/__init__.py @@ -1 +1 @@ -'''Base and built-in Processors.''' +"""A sub-package containing base and built-in processors.""" diff --git a/source/corelay/processor/affinity.py b/source/corelay/processor/affinity.py index 26f851d..14307ff 100644 --- a/source/corelay/processor/affinity.py +++ b/source/corelay/processor/affinity.py @@ -1,97 +1,107 @@ -"""Affinity (similarity) processors. +"""Affinity (similarity) processors.""" -""" -import logging +from typing import Annotated, Any -import numpy as np -from scipy import sparse as sp +import numpy +import scipy.sparse -from .base import Processor, Param - -LOGGER = logging.getLogger(__name__) +from corelay.base import Param +from corelay.processor.base import Processor class Affinity(Processor): - """Base class for Affinity (Similarity) classes. + """The abstract base class for processors that compute affinity (i.e., similarity) matrices. - Each subclass implements a __call__ function to compute its corresponding affinity matrix of some data. + Note: + Each sub-class has to implement a ``__call__`` method to compute its corresponding affinity matrix of some data. + Args: + is_output (bool, optional): A value indicating whether this ``Affinity`` processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. """ class SparseKNN(Affinity): - """Sparse K-Nearest-Neighbors affinity + """A processor for computing an affinity matrix using the sparse k-nearest neighbors (KNN) method. + + Args: + is_output (bool, optional): A value indicating whether this ``SparseKNN`` affinity processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + n_neighbors (int, optional): Number of neighbors to consider. Defaults to 10. + symmetric (bool, optional): If `True`, the affinity matrix is set to the mean of itself and its transpose. Defaults to `True`. + """ - Parameters - ---------- - n_neighbors : int - Number of neighbors to consider. - symmetrix : bool - If `True`, Affinity matrix is set to the mean of itself and itself transposed. + n_neighbors: Annotated[int, Param(int, 10, identifier=True)] + """A parameter for the number of neighbors to consider. Defaults to 10.""" - """ - n_neighbors = Param(int, 10, identifier=True) - symmetric = Param(bool, True, identifier=True) + symmetric: Annotated[bool, Param(bool, True, identifier=True)] + """A parameter for whether to make the affinity matrix symmetric. Defaults to `True`.""" - def function(self, data): + def function(self, data: Any) -> Any: """Compute Sparse K-Nearest-Neighbors affinity matrix. - Parameters - ---------- - data : :obj:`numpy.ndarray` - Distance matrix used to compute affinity matrix. - - Returns - ------ - :obj:`sp.csr_matrix` - Sparse CSR representation of KNN affinity matrix + Args: + data (Any): A NumPy array ``numpy.ndarray`` containing the pairwise distances between samples, which is used to compute the affinity + matrix. + Returns: + Any: Returns a sparse CSR representation ``scipy.sparse.csr_matrix`` of the KNN affinity matrix. """ - k = self.n_neighbors - # number of samples - n = data.shape[0] - # silently use maximum number of neighbors if k is larger - k = min(k, n - 1) + number_of_neighbors = self.n_neighbors + number_of_samples = data.shape[0] + + # Silently uses the maximum number of neighbors if the specified number of neighbors is larger than the number of available samples + number_of_neighbors = min(number_of_neighbors, number_of_samples - 1) + + # Sets up indices for a sparse representation of the nearest neighbors + columns = data.argsort(1)[:, 1:number_of_neighbors + 1] + rows = numpy.mgrid[:number_of_samples, :number_of_neighbors][0] - # set up indices for sparse representation of nearest neighbors - cols = data.argsort(1)[:, 1:k + 1] - rows = np.mgrid[:n, :k][0] - # existing edges are denoted with ones - vals = np.ones((n, k), dtype=data.dtype) - affinity = sp.csr_matrix((vals.flat, (rows.flat, cols.flat)), shape=(n, n)) + # Denotes the existing edges with ones + values = numpy.ones((number_of_samples, number_of_neighbors), dtype=data.dtype) + affinity = scipy.sparse.csr_matrix((values.flat, (rows.flat, columns.flat)), shape=(number_of_samples, number_of_samples)) - # make the affinity matrix symmetric + # Makes the affinity matrix symmetric if self.symmetric: - affinity = (affinity + affinity.T) / 2. + affinity = (affinity + affinity.T) / 2.0 + return affinity class RadialBasisFunction(Affinity): - """Radial Basis Function affinity - - Parameters - ---------- - sigma : float - RBF scale - + """A processor for computing an affinity matrix using the Radial Basis Function (RBF) kernel. + + Args: + is_output (bool, optional): A value indicating whether this ``RadialBasisFunction`` affinity processor is the output of a ``Pipeline``. + Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + sigma (float, optional): The scale of the RBF kernel. Defaults to 1.0. """ - sigma = Param(float, 1.0, identifier=True) - def function(self, data): - """Compute Radial Basis Function affinity matrix. + sigma: Annotated[float, Param(float, 1.0, identifier=True)] + """A parameter for the scale of the RBF kernel. Defaults to 1.0.""" - Parameters - ---------- - data : :obj:`numpy.ndarray` - Distance matrix used to compute affinity matrix. + def function(self, data: Any) -> Any: + """Compute Radial Basis Function affinity matrix. - Returns - ------ - :obj:`np.ndarray` - Dense RBF affinity matrix + Args: + data (Any): A NumPy array ``numpy.ndarray`` containing the pairwise distances between samples, which is used to compute the affinity + matrix. The data is expected to be a square matrix of shape (number_of_samples, number_of_samples). + Returns: + Any: Returns a NumPy array ``numpy.ndarray`` containing the RBF affinity matrix. """ - sigma = self.sigma - affinity = np.exp(-data / (2 * sigma ** 2)) + + affinity = numpy.exp(-data / (2 * self.sigma ** 2)) return affinity diff --git a/source/corelay/processor/base.py b/source/corelay/processor/base.py index 0229722..d26a9cd 100644 --- a/source/corelay/processor/base.py +++ b/source/corelay/processor/base.py @@ -1,100 +1,127 @@ -"""Base classes Param and Processor. +"""A module that contains the base classes ``Param`` and ``Processor``.""" -""" import inspect -from types import FunctionType, MethodType, LambdaType -from abc import abstractmethod +from abc import ABC, abstractmethod from collections import OrderedDict +from collections.abc import Callable +from types import FunctionType, LambdaType +from typing import Annotated, Any -from ..io import Storable, NoStorage, NoDataSource, NoDataTarget -from ..base import Param -from ..plugboard import Plugboard +from corelay.base import Param +from corelay.io import NoStorage, NoDataSource, NoDataTarget +from corelay.io.storage import Storable +from corelay.plugboard import Plugboard -class Processor(Plugboard): - """Base class of processors of tasks in a pipeline instance. +class Processor(ABC, Plugboard): + """The abstract base class of processors of tasks in a pipeline instance.""" - Attributes - ---------- - is_output : bool - Assigned as :obj:`Param`, will be assigned as an instance attribute in `__init__`. - Defines whether the Processor should yield an output for a Pipeline. - is_checkpoint : bool - Assigned as :obj:`Param`, will be assigned as an instance attribute in `__init__`. - Defines whether check-pointed pipeline computations should start at this point, if there exists a previously - computed checkpoint value. - checkpoint_data : object - If this Processor is a checkpoint, and if the Processor was called at least once, stores the output of this - processor. + is_output: Annotated[bool, Param(bool, False)] + """Contains a value indicating whether this ``Processor`` is the output of a ``Pipeline``.""" + is_checkpoint: Annotated[bool, Param(bool, False)] + """Contains a value indicating whether check-pointed pipeline computations should start at this point, if there exists a previously computed + checkpoint value. """ - is_output = Param(bool, False) - is_checkpoint = Param(bool, False) - io = Param(Storable, NoStorage()) - - def __init__(self, *args, **kwargs): - """Initialize all :obj:`Param` defined parameters to either there default value or, if supplied as keyword - argument, to the value supplied. - - Parameters - ---------- - is_checkpoint : bool - Whether the Processor should yield an output for a Pipeline. - is_output : bool - Whether check-pointed pipeline computations should start at this point, if there exists a previously computed - checkpoint value. - *args : list - Params that have been flagged as positional, in their order of declaration. - **kwargs : dict - Other potential parameters defined in sub classes. + io: Annotated[Storable, Param(Storable, NoStorage())] + """Contains an IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this run or in subsequent runs + of the ``Pipeline``. + """ + + checkpoint_data: Any + """If this ``Processor`` is a checkpoint, and if the processor was called at least once, stores the output of this processor.""" + + def __init__( + self, + *args: Any, + is_output: bool | None = None, + is_checkpoint: bool | None = None, + io: Storable | None = None, + **kwargs: Any + ) -> None: + """Initializes a new ``Processor`` instance. All defined ``Param`` class attributes are initialized either to their respective default values + or, if supplied as keyword argument, to the value supplied. + + Args: + *args (Any): A list of the positional arguments, which will be used to initialize the parameters of the ``Processor`` that were marked as + positional. + is_output (bool | None, optional): A value indicating whether this ``Processor`` is the output of a ``Pipeline``. If `None` is specified, + the corresponding ``Param`` will default to its defined default value, which is `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. If `None` is specified, the corresponding ``Param`` will default to its defined default + value, which is `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. If `None` is specified, the corresponding ``Param`` will default to its defined default + value, which is an instance of ``NoStorage``. + **kwargs (Any): A dictionary of keyword arguments, which will be used to initialize the parameters of the ``Processor`` that were marked + as keyword arguments. The keys of the dictionary are the names of the parameters, and the values are the values to be assigned to + those parameters. + + Raises: + TypeError: The number of positional arguments supplied is greater than the number of parameters that were marked as positional or a + parameter was defined as both positional and a keyword argument. """ - positions = dict(zip((name for name, param in self.collect(Param).items() if param.is_positional), args)) - if len(positions) < len(args): - raise TypeError(f'Expected at most {len(positions)} positional arguments, got {len(args)}') - for name in positions: + + # Pairs the parameters that are marked as positional with their corresponding positional arguments; this is a dictionary where the keys are + # the names of the parameters and the values are the corresponding positional arguments + parameter_arguments = dict(zip((name for name, parameter in self.collect(Param).items() if parameter.is_positional), args)) + if len(parameter_arguments) < len(args): + raise TypeError(f'Expected at most {len(parameter_arguments)} positional arguments, got {len(args)}.') + + # PyDocLint does not support the documentation of the constructor parameters both in the __init__ method and the class docstring, so we have + # to add the documentation for the is_output, is_checkpoint, and io parameters here; therefore, the parameters have to be added to the keyword + # arguments manually + kwargs['is_output'] = is_output + kwargs['is_checkpoint'] = is_checkpoint + kwargs['io'] = io + + # Checks if any of the positional arguments were also specified as keyword arguments + for name in parameter_arguments: if name in kwargs: - raise TypeError(f"Argument was specified as both positional and keyword: '{name}'") - positions.update(kwargs) - super().__init__(**positions) - self.checkpoint_data = None + raise TypeError(f'Argument "{name}" was specified as both positional and a keyword argument.') + + # Adds the keyword arguments to the parameter arguments + parameter_arguments.update(kwargs) + + # Calls the constructor of the parent class, Plugboard, with the parameter arguments as keyword arguments + super().__init__(**parameter_arguments) + + # Initializes the checkpoint data to None + self.checkpoint_data: Any = None @abstractmethod - def function(self, data): - """Abstract function this Processor should apply on input - - Parameters - ---------- - data : object - Input data to this Processor. - - Raises - ------ - NotImplementedError - Always, since this is an abstract function. + def function(self, data: Any) -> Any: + """Applies a function to the input data. This function should be implemented by subclasses of ``Processor``. + + Args: + data (Any): The input data to this ``Processor``. + + Raises: + NotImplementedError: This is an abstract method and should be implemented by subclasses of ``Processor`` and therefore always raises the + ``NotImplementedError`` exception. + + Returns: + Any: Returns the output of the function applied to the input data. """ - def __call__(self, data): - """Apply `self.function` on input data, save output if `self.is_checkpoint` + raise NotImplementedError('This is an abstract method and should be implemented by subclasses of Processor.') - Parameters - ---------- - data : object - Input data to this Processor. + def __call__(self, data: Any) -> Any: + """Apply ``function`` on the input data and saves the output if the ``is_checkpoint`` ``Param`` was set to `True`. - Returns - ------- - object - Depending on what operation `self.function` executes. + Args: + data (Any): The input data to this ``Processor``. + Returns: + Any: Returns the output of the function applied to the input data. """ + try: - # pylint: disable=no-member out = self.io.read(data_in=data, meta=self.identifiers()) except NoDataSource: out = self.function(data) try: - # pylint: disable=no-member self.io.write(data_out=out, data_in=data, meta=self.identifiers()) except NoDataTarget: pass @@ -102,118 +129,151 @@ def __call__(self, data): self.checkpoint_data = out return out - def param_values(self): + def param_values(self) -> dict[str, Any]: """Get values for all parameters defined through :obj:`Param` attributes. - Returns - ------- - dict of object - Dict of the instance values of defined parameters. - + Returns: + dict[str, Any]: Returns a dictionary containing the names of the parameters as keys and their values as values. """ + return self.collect_attr(Param) - def identifiers(self): + def identifiers(self) -> OrderedDict[str, Any]: """Returns a dict containing the class qualifier name, as well all Parameters marked as identifiers with their values - Returns - ------- - :obj:`collections.OrderedDict` - OrderedDict, containing the qualifier class name and all Parameters marked as identifiers with their values + Returns: + OrderedDict[str, Any]: Returns an ordered dictionary, containing the class qualifier name and all parameters marked as identifiers with + their values. """ + result = OrderedDict(name=type(self).__qualname__) - result.update((key, getattr(self, key)) for key, param in self.collect(Param).items() if param.is_identifier) + result.update((key, getattr(self, key)) for key, parameter in self.collect(Param).items() if parameter.is_identifier) return result - def copy(self): - """Copy self, creating a new Processor instance with the same values for :obj:`Param` attribute defined - parameters. - - Returns - ------- - :obj:`Processor` - New instance with copied parameter values. + def copy(self) -> 'Processor': + """Copies this processor, by creating a new ``Processor`` instance with the same values for the parameters defined as ``Param`` class + attributes and the same checkpoint data. + Returns: + Processor: Returns a copy of this ``Processor`` instance. """ - new = type(self)(**self.param_values()) - new.checkpoint_data = self.checkpoint_data - return new + + new_processor = type(self)(**self.param_values()) + new_processor.checkpoint_data = self.checkpoint_data + return new_processor @property - def _output_repr(self): - return 'output:np.ndarray' + def _output_repr(self) -> str: + """Gets a string for the output of the ``Processor``. + + Returns: + str: Returns the string representation of the output of the ``Processor``. + """ - def __repr__(self): - """Return Processor's representation. - I.e.: ProcessorName(metric=sqeuclidean, function=lambda x: x.mean(1)) -> output: np.ndarray - Replace self._output_repr for output representation. Default: output:np.ndarray. + return 'output: numpy.ndarray' + def __repr__(self) -> str: + """Generates a string representation of the ``Processor`` instance, including the class name, the parameters and their values, and the output + representation, e.g., `ProcessorName(metric=sqeuclidean, function=lambda x: x.mean(1)) -> output: numpy.ndarray`. + + Returns: + str: Returns a string representation of the ``Processor`` instance. """ - def transform(x): - """If x is a lambda function, return the source. + def get_source_code_for_lambda_expression(lambda_expression: LambdaType | Any) -> str: + """Retrieves the source code of the specified lambda expression. If the argument is not a lambda expression, its string representation is + returned. + + Args: + lambda_expression (LambdaType | Any): The lambda expression for which the source code should be retrieved. + Returns: + str: Returns the source code of the lambda expression as a string, or a string representation of the argument if it is not a lambda + expression. """ - if isinstance(x, LambdaType): - x = inspect.getsource(x).split('=', 1)[1].strip() - return x + + if isinstance(lambda_expression, LambdaType): + return inspect.getsource(lambda_expression).split('=', 1)[1].strip() + return str(lambda_expression) name = self.__class__.__name__ - params = ', '.join([f'{k}={transform(v)}' for k, v in self.param_values().items() if v]) - return f'{name}({params}) -> {self._output_repr}' + parameters = ', '.join([f'{k}={get_source_code_for_lambda_expression(v)}' for k, v in self.param_values().items() if v]) + return f'{name}({parameters}) -> {self._output_repr}' class FunctionProcessor(Processor): - """Processor instance initialized with a supplied function - - Attributes - ---------- - function : :obj:`types.FunctionType` or :obj:`types.MethodType` - The function around which to create the :obj:`FunctionProcessor`. It wil be bound as a method if bind_method. - bind_method : bool - Will bind `function` to this class, enabling it to access `self`. + """A ``Processor`` that executes a user-defined function. + + Args: + processing_function (FunctionType): The function around which to create the :obj:`FunctionProcessor`. This function will be invoked when the + ``function`` method is invoked or the ``FunctionProcessor`` object is called like a function. Depending on whether ``bind_method`` is + `True` or `False`, it wil be bound as a method to the ``FunctionProcessor`` object. + is_output (bool, optional): A value indicating whether this ``FunctionProcessor`` is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + bind_method (bool, optional): A value indicating whether the ``processing_function`` will be bound to this class, enabling it to access + `self`. Defaults to `False`. + """ + processing_function: Annotated[FunctionType, Param(FunctionType, lambda _self, data: data, positional=True)] + """The function around which to create the :obj:`FunctionProcessor`. This function will be invoked when the ``function`` method is invoked or the + ``FunctionProcessor`` object is called like a function. Depending on whether ``bind_method`` is `True` or `False`, it wil be bound as a method to + the ``FunctionProcessor`` object. """ - function = Param((MethodType, FunctionType), (lambda self, data: data), positional=True) - bind_method = Param(bool, False) - def __call__(self, data): - """Bind function as attribute of instance. + bind_method: Annotated[bool, Param(bool, False)] + """A value indicating whether the ``processing_function`` will be bound to this class, enabling it to access `self`.""" + + def function(self, data: Any) -> Any: + """Invokes the function bound to this class with the input data. - See also - -------- - :obj:`Processor` + Note: + In a previous version of CoRelAy, the ``processing_function`` was actually bound to the class in the ``__call__`` method, but this caused + typing issues, as static type checkers like MyPy believed that the ``FunctionProcessor`` class was still abstract, as it did not + explicitly override the ``function`` method. The ``processing_function`` used to be called just ``function``, which meant, that during + runtime, functionally, the ``function`` method was overridden, as its slot would have been taken by the ``function`` parameter. + Statically, however, this was not the case. Overriding the ``function`` method and still binding the ``processing_function`` to the class + in the ``__call__`` method causes more typing issues, as the static type checker does not allow us to write to a method slot. For this + reason, the ``function`` method was overridden and internally calls the ``processing_function`` method with `self` as the first argument. + Functionally, this should be equivalent to the previous version, but it is not guaranteed that it is in every use case. This might have + rethought and changed in the future. + Args: + data (Any): The input data to this ``Processor``. + + Returns: + Any: Returns the output of the function applied to the input data. """ + if self.bind_method: - self.function = self.function.__get__(self, type(self)) - return super().__call__(data) + return self.processing_function(self, data) + return self.processing_function(data) -def ensure_processor(proc, **kwargs): - """Make sure argument is a Processor and, if it is not, but callable, make it a FunctionProcessor. Set attributes of - resulting processor as stated in `**kwargs`. +def ensure_processor(processor_or_callable: Processor | Callable[..., Any], **kwargs: Any) -> Processor: + """Ensures that the specified processor or callable argument ``processor_or_callable`` is of type ``Processor`` and, if it is not, but callable, + make it a ``FunctionProcessor``. Sets the attributes of resulting processor as stated in `**kwargs`. - Parameters - ---------- - proc : Processor or callable - Object to ensure to be a :obj:`Processor`. - **kwargs : - Keyword arguments stating new default values for Parameters of `proc`. + Args: + processor_or_callable (Processor | Callable[..., Any]): The processor or callable for which to ensure that it is a ``Processor``. + **kwargs (Any): The keyword arguments to be passed to the ``Processor``. These keyword arguments are used to set the values of the parameters + of the ``Processor``. - Returns - ------- - :obj:`Processor` - Original object `proc` with updated attributes if it was a Processor, else a :obj:`FunctionProcessor` with - supplied function. - `proc` and attributes as given in `**kwargs`. + Raises: + TypeError: The supplied processor or callable ``processor_or_callable`` is neither a ``Processor`` nor callable. + Returns: + Processor: Returns the original ``processor_or_callable`` if it was a ``Processor``, or a new ``FunctionProcessor``, which calls it if it was + a callable. The attributes of the resulting processor are set as stated in `**kwargs`. """ - if not isinstance(proc, Processor): - if callable(proc): - proc = FunctionProcessor(function=proc) + + if not isinstance(processor_or_callable, Processor): + if callable(processor_or_callable): + processor_or_callable = FunctionProcessor(processing_function=processor_or_callable) else: - raise TypeError(f'Supplied processor {proc} is neither a Processor, nor callable!') - proc.update_defaults(**kwargs) - return proc + raise TypeError(f'Supplied processor {processor_or_callable} is neither a Processor, nor callable!') + processor_or_callable.update_defaults(**kwargs) + return processor_or_callable diff --git a/source/corelay/processor/clustering.py b/source/corelay/processor/clustering.py index 69f073c..91b4225 100644 --- a/source/corelay/processor/clustering.py +++ b/source/corelay/processor/clustering.py @@ -1,167 +1,402 @@ -"""Clustering Processors +"""A module that contains processors for clustering algorithms.""" -""" import io import os -import logging +from collections.abc import Callable +from typing import Annotated, Any, Literal, SupportsIndex +import scipy.cluster.hierarchy as shc import sklearn.cluster +from numpy.typing import NDArray from matplotlib import pyplot as plt -import scipy.cluster.hierarchy as shc +from sklearn.base import ClusterMixin -from .base import Processor, Param -from ..utils import import_or_stub -hdbscan = import_or_stub('hdbscan') # pylint: disable=invalid-name +from corelay.base import Param +from corelay.processor.base import Processor +from corelay.utils import import_or_stub -LOGGER = logging.getLogger(__name__) +hdbscan: Callable[..., ClusterMixin] = import_or_stub('hdbscan', 'HDBSCAN') +"""Performs the HDBSCAN clustering algorithm on a vector or distance matrix. + +Returns: + ClusterMixin: Returns an HDBSCAN cluster estimator, which can be used to fit the data. + +Note: + Since the HDBSCAN library is an optional dependency of CoRelAy, it is imported using the ``import_or_stub`` function, which tries to import the + module/type/function specified. If the import fails, it returns a stub instead, which will raise an exception when used. The exception message + will tell users how to install the missing dependencies for the functionality to work. +""" -class Clustering(Processor): - """Clustering Processor +class Clustering(Processor): + """The abstract base class for ``Processor`` that performs clustering. + + Args: + is_output (bool, optional): A value indicating whether this ``Clustering`` processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the clustering algorithm. Defaults to an empty dictionary. """ - kwargs = Param(dict, default={}) + + kwargs: Annotated[dict[str, Any], Param(dict, default={})] + """A dictionary of additional keyword arguments for the clustering algorithm. Defaults to an empty dictionary.""" class KMeans(Clustering): - """KMeans Clustering + """A clustering ``Processor`` that performs the k-Means clustering algorithm. + + Args: + is_output (bool, optional): A value indicating whether this ``KMeans`` clustering processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the k-Means clustering algorithm. Defaults to an empty dictionary. + n_clusters (int, optional): The number of clusters to form. Defaults to 2. + index (tuple[int | slice], optional): The indices of the data to be clustered. Defaults to an empty slice. + + See Also: + :obj:`sklearn.cluster.KMeans` + """ - Parameters - ---------- - n_clusters: int - Default: 2 - index: tuple - Default: empty slice - kwargs: dict - See also: :obj:`sklearn.cluster.KMeans` + n_clusters: Annotated[int, Param(int, 2, identifier=True)] + """The number of clusters to form. Defaults to 2.""" - See Also - -------- - :obj:`sklearn.cluster.KMeans` + index: Annotated[SupportsIndex | tuple[SupportsIndex, ...], Param((SupportsIndex, tuple), (slice(None),))] + """The indices of the data to be clustered. Defaults to an empty slice.""" - """ - n_clusters = Param(int, 2, identifier=True) - index = Param(tuple, (slice(None),)) + def function(self, data: Any) -> Any: + """Performs k-Means clustering on the data. + + Args: + data (Any): The data to be clustered. The data should be a NumPy array or a sparse matrix. + + Returns: + Any: Returns the a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a + cluster assigned to the data point. + """ - def function(self, data): - # pylint: disable=not-a-mapping - return sklearn.cluster.KMeans(n_clusters=self.n_clusters, **self.kwargs).fit_predict(data[self.index]) + input_data: NDArray[Any] = data + return sklearn.cluster.KMeans(n_clusters=self.n_clusters, **self.kwargs).fit_predict(input_data[self.index]) + + +HDBSCANDistanceMeasure = Literal[ + 'arccos', + 'braycurtis', + 'canberra', + 'chebyshev', + 'cityblock', + 'cosine', + 'dice', + 'euclidean', + 'hamming', + 'haversine', + 'infinity', + 'jaccard', + 'kulsinski', + 'l1', + 'l2', + 'mahalanobis', + 'manhattan', + 'matching', + 'minkowski', + 'p', + 'rogerstanimoto', + 'russellrao', + 'seuclidean', + 'sokalmichener', + 'sokalsneath', + 'wminkowski' +] +"""An enumeration of the distance measures supported by HDBSCAN.""" class HDBSCAN(Clustering): - """HDBSCAN clustering + """A clustering ``Processor`` that performs the HDBSCAN clustering algorithm. + + Args: + is_output (bool, optional): A value indicating whether this ``HDBSCAN`` clustering processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the HDBSCAN clustering algorithm. Defaults to an empty dictionary. + n_clusters (int, optional): The number of clusters to form. Defaults to 2. + metric (HDBSCANDistanceMeasure, optional): The distance metric to use. This can be one of "euclidean", "l2" (equivalent to "euclidean"), + "minkowski", "p" (equivalent to "minkowski"), "manhattan", "cityblock" (equivalent to "manhattan"), "l1" (equivalent to "manhattan"), + "chebyshev", "infinity" (equivalent to "chebyshev"), "seuclidean", "mahalanobis", "wminkowski", "hamming", "canberra", "braycurtis", + "matching", "jaccard", "dice", "kulsinski", "rogerstanimoto", "russellrao", "sokalmichener", "sokalsneath", "haversine", "cosine" (since + the cosine distance is not a true distance measure, it is not supported; using "cosine" will use the "arccos" distance instead), and + "arccos". Defaults to "euclidean". + + See Also: + :obj:`hdbscan.HDBSCAN` + + Notes: + GitHub repository including documentation for HDBSCAN: https://github.com/scikit-learn-contrib/hdbscan. + """ - Parameters - ---------- - n_clusters: int - Default: 2 - metric: str - Default: euclidean - kwargs: dict - See also: :obj:`hdbscan.HDBSCAN` + n_clusters: Annotated[int, Param(int, 5, identifier=True)] + """The number of clusters to form. Defaults to 5.""" - See Also - -------- - :obj:`hdbscan.HDBSCAN` + metric: Annotated[HDBSCANDistanceMeasure, Param(str, 'euclidean', identifier=True)] + """The distance metric to use. Defaults to "euclidean".""" - Notes - ----- - https://github.com/scikit-learn-contrib/hdbscan + def function(self, data: Any) -> Any: + """Performs the HDBSCAN clustering algorithm on the data. - """ - n_clusters = Param(int, 5, identifier=True) - metric = Param(str, 'euclidean', identifier=True) + Args: + data (Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or a sparse + matrix. - def function(self, data): - # pylint: disable=not-a-mapping - clustering = hdbscan.HDBSCAN(min_cluster_size=self.n_clusters, metric=self.metric, **self.kwargs) + Returns: + Any: Returns a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a cluster + assigned to the data point. + """ + + clustering = hdbscan(min_cluster_size=self.n_clusters, metric=self.metric, **self.kwargs) return clustering.fit_predict(data) +DBSCANDistanceMeasure = Literal[ + 'cityblock', + 'cosine', + 'euclidean', + 'haversine', + 'l1', + 'l2', + 'manhattan', + 'nan_euclidean' + 'precomputed', +] +"""An enumeration of the distance measures supported by DBSCAN.""" + + class DBSCAN(Clustering): - """DBSCAN clustering - - Parameters - ---------- - metric: str - Default: euclidean - eps: float - Default: 0.5 - min_samples: int - Default: 5 - kwargs: dict - See also: :obj:`sklearn.cluster.DBSCAN` - - See Also - -------- - :obj:`sklearn.cluster.DBSCAN` + """A clustering ``Processor`` that performs the DBSCAN clustering algorithm. + + Args: + is_output (bool, optional): A value indicating whether this ``DBSCAN`` clustering processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the DBSCAN clustering algorithm. Defaults to an empty dictionary. + metric (str, optional): The distance metric to use. Defaults to "euclidean". + eps (float, optional): The maximum distance between two samples for one to be considered as in the neighborhood of the other. This is not a + maximum bound on the distances of points within a cluster. This is the most important DBSCAN parameter to choose appropriately for your + data set and distance function. Defaults to 0.5. + min_samples (int, optional): The number of samples (or total weight) in a neighborhood for a point to be considered as a core point. This + includes the point itself. If ``min_samples`` is set to a higher value, DBSCAN will find denser clusters, whereas if it is set to a lower + value, the found clusters will be more sparse. Defaults to 5. + + See Also: + :obj:`sklearn.cluster.DBSCAN` + """ + + metric: Annotated[DBSCANDistanceMeasure, Param(str, 'euclidean', identifier=True)] + """The distance metric to use. Defaults to "euclidean".""" + + eps: Annotated[float, Param(float, 0.5, identifier=True)] + """The maximum distance between two samples for one to be considered as in the neighborhood of the other. This is not a maximum bound on the + distances of points within a cluster. This is the most important DBSCAN parameter to choose appropriately for your data set and distance function. + Defaults to 0.5. + """ + min_samples: Annotated[int, Param(int, 5, identifier=True)] + """The number of samples (or total weight) in a neighborhood for a point to be considered as a core point. This includes the point itself. If + ``min_samples`` is set to a higher value, DBSCAN will find denser clusters, whereas if it is set to a lower value, the found clusters will be more + sparse. Defaults to 5. """ - metric = Param(str, 'euclidean', identifier=True) - eps = Param(float, 0.5, identifier=True) - min_samples = Param(int, 5, identifier=True) - - def function(self, data): - # pylint: disable=not-a-mapping - clustering = sklearn.cluster.DBSCAN(eps=self.eps, min_samples=self.min_samples, metric=self.metric, - **self.kwargs) + + def function(self, data: Any) -> Any: + """Performs the DBSCAN clustering algorithm on the data. + + Args: + data (Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or a sparse + matrix. + + Returns: + Any: Returns a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a cluster + assigned to the data point. Noisy points will be labeled as -1. + """ + + clustering = sklearn.cluster.DBSCAN( + eps=self.eps, + min_samples=self.min_samples, + metric=self.metric, + **self.kwargs + ) return clustering.fit_predict(data) +AgglomerativeClusteringDistanceMeasure = Literal[ + 'euclidean', + 'l1', + 'l2', + 'manhattan', + 'cosine', + 'precomputed' +] +"""An enumeration of the distance measures supported by the Agglomerative Clustering algorithm.""" + + +AgglomerativeClusteringLinkageMethod = Literal['ward', 'complete', 'average', 'single'] +"""An enumeration of the linkage method supported by the Agglomerative Clustering algorithm. The linkage method is used to determine the distance +between two clusters when performing hierarchical clustering. The Agglomerative Clustering algorithm will merge the pairs of clusters that minimize +this method. The following linkage methods are supported: + +- "ward" minimizes the variance of the clusters being merged. +- "average" uses the average of the distances of each observation of the two clusters. +- "complete" uses the maximum distances between all observations of the two clusters. +- "single" uses the minimum of the distances between all observations of the two clusters. +""" + + class AgglomerativeClustering(Clustering): - """Agglomerative clustering - - Parameters - ---------- - n_clusters: int - Default: 5 - metric: str - Options: "euclidean", "l1", "l2", "manhattan", "cosine", or 'precomputed'. Default: "euclidean" - linkage: str - Options: "ward", "complete", "average", "single". Default: "ward" - kwargs: dict - See also: :obj:`sklearn.cluster.AgglomerativeClustering` - - See Also - -------- - :obj:`sklearn.cluster.AgglomerativeClustering` + """A clustering ``Processor`` that performs the Agglomerative Clustering algorithm. + + Args: + is_output (bool, optional): A value indicating whether this ``AgglomerativeClustering`` clustering processor is the output of a ``Pipeline``. + Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the agglomerative clustering algorithm. Defaults to an empty dictionary. + n_clusters (int, optional): The number of clusters to form. Defaults to 5. + metric (AgglomerativeClusteringDistanceMeasure, optional): The distance metric to use. Defaults to "euclidean". + linkage (AgglomerativeClusteringLinkageMethod, optional): The linkage method to use. This determines which distance to use between two newly + formed clusters. The algorithm will merge the pairs of clusters that minimize this method. Defaults to "ward". + + See Also: + :obj:`sklearn.cluster.AgglomerativeClustering` + """ + n_clusters: Annotated[int, Param(int, 5, identifier=True)] + """The number of clusters to form. Defaults to 5.""" + + metric: Annotated[AgglomerativeClusteringDistanceMeasure, Param(str, 'euclidean', identifier=True)] + """The distance metric to use. Defaults to "euclidean".""" + + linkage: Annotated[AgglomerativeClusteringLinkageMethod, Param(str, 'ward', identifier=True)] + """The linkage method to use. This determines which distance to use between two newly formed clusters. The algorithm will merge the pairs of + clusters that minimize this method. Defaults to "ward". """ - n_clusters = Param(int, 5, identifier=True) - metric = Param(str, 'euclidean', identifier=True) - linkage = Param(str, 'ward', identifier=True) - - def function(self, data): - # pylint: disable=not-a-mapping - clustering = sklearn.cluster.AgglomerativeClustering(n_clusters=self.n_clusters, metric=self.metric, - linkage=self.linkage, **self.kwargs) + + def function(self, data: Any) -> Any: + """Performs the Agglomerative Clustering algorithm on the data. + + Args: + data (Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or + `(number_of_samples, number_of_samples)`, or a sparse matrix. + + Returns: + Any: _description_ + """ + + clustering = sklearn.cluster.AgglomerativeClustering( + n_clusters=self.n_clusters, + metric=self.metric, + linkage=self.linkage, + **self.kwargs + ) return clustering.fit_predict(data) +DendrogramDistanceMeasure = Literal[ + 'braycurtis', + 'canberra', + 'chebyshev', + 'cityblock', + 'correlation', + 'cosine', + 'dice', + 'euclidean', + 'hamming', + 'jaccard', + 'jensenshannon', + 'kulczynski1', + 'mahalanobis', + 'minkowski', + 'rogerstanimoto', + 'russellrao', + 'seuclidean', + 'sokalmichener', + 'sokalsneath', + 'sqeuclidean', + 'yule' +] +"""An enumeration of the distance measures supported by the dendrogram clustering ``Processor``.""" + + +DendrogramLinkageMethod = Literal['ward', 'average', 'complete', 'single', 'centroid', 'median', 'weighted'] +"""An enumeration of the linkage method supported by the hierarchical clustering algorithm used by the Dendrogram ``Processor``. The linkage method is +used to determine the distance between two newly formed clusters when performing hierarchical clustering. The hierarchical clustering algorithm used +by the Dendrogram ``Processor`` will merge the pairs of clusters that minimize this method. The following linkage methods are supported: + +- "ward" minimizes the variance of the clusters being merged. +- "average" uses the average of the distances of each observation of the two clusters. +- "complete" uses the maximum distances between all observations of the two clusters. +- "single" uses the minimum of the distances between all observations of the two clusters. +- "centroid" the centroid of the new cluster that would be formed by merging the two clusters. +- "median" uses the median of the centroids of the two clusters. +- "weighted" assigns the weighted distance between the two original clusters and a third remaining cluster to the new cluster. +""" + + class Dendrogram(Clustering): - """Dendrogram. + """A clustering ``Processor`` that generates plots the hierarchical clustering as a dendrogram. + + Args: + is_output (bool, optional): A value indicating whether this ``Dendrogram`` clustering processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the hierarchical clustering algorithm. Defaults to an empty dictionary. + output_file (str | bytes | os.PathLike[Any] | io.IOBase): The path to a file or a file descriptor to save the dendrogram plot to. + metric (DendrogramDistanceMeasure, optional): The distance metric to use for the clustering. Defaults to "euclidean". + linkage (DendrogramLinkageMethod, optional): The linkage criterion to use. This determines which distance to use between sets of observation. + Defaults to "ward". + """ - Parameters - ---------- - output_path: str - Path to where the dendrogram is saved, - metric: str - Options: "euclidean", "l1", "l2", "manhattan", "cosine", or 'precomputed'. Default: "euclidean" - linkage: str - Options: "ward", "complete", "average", "single". Default: "ward" + output_file: Annotated[str | bytes | os.PathLike[Any] | io.IOBase, Param((str, bytes, os.PathLike, io.IOBase), mandatory=True)] + """The path to a file or a file descriptor to save the dendrogram plot to.""" + metric: Annotated[DendrogramDistanceMeasure, Param(str, 'euclidean')] + """The distance metric to use for the clustering. This can be one of "braycurtis", "canberra", "chebyshev", "cityblock", "correlation", "cosine", + "dice", "euclidean", "hamming", "jaccard", "jensenshannon", "kulczynski1", "mahalanobis", "minkowski", "rogerstanimoto", "russellrao", + "seuclidean", "sokalmichener", "sokalsneath", "sqeuclidean", or "yule". Defaults to "euclidean". """ - output_file = Param((str, io.IOBase), mandatory=True) - metric = Param(str, 'euclidean') - linkage = Param(str, 'ward') - def function(self, data): - """Saves Dendrogram by default to /tmp/dendrogram.png and returns the input data. + linkage: Annotated[DendrogramLinkageMethod, Param(str, 'ward')] + """The linkage criterion to use. This determines which distance to use between sets of observation. Defaults to "ward".""" + + def function(self, data: Any) -> Any: + """Performs the hierarchical clustering algorithm on the data and generates a dendrogram plot. + + Args: + data (Any): The data to be clustered. The data should be a NumPy array that contains a condensed distance matrix. A condensed distance + matrix is a flat array containing the upper triangular of the distance matrix. Alternatively, an array of shape + `(number_of_observations, number_of_dimensions)` may be passed in. + Returns: + Any: Returns the data that was passed in. The data is not modified. """ + if isinstance(self.output_file, (str, bytes, os.PathLike)): os.makedirs(os.path.dirname(self.output_file), exist_ok=True) + plt.figure(figsize=(10, 7)) shc.dendrogram(shc.linkage(data, method=self.linkage)) plt.savefig(self.output_file) + return data diff --git a/source/corelay/processor/distance.py b/source/corelay/processor/distance.py index 467e956..6c3a2b3 100644 --- a/source/corelay/processor/distance.py +++ b/source/corelay/processor/distance.py @@ -1,32 +1,84 @@ -"""Pair-wise distance processors +"""A module that contains pair-wise distance ``Processors``.""" -""" -import logging +from typing import Annotated, Any, Literal +from numpy.typing import NDArray from scipy.spatial.distance import pdist, squareform -from .base import Processor, Param - -LOGGER = logging.getLogger(__name__) +from corelay.base import Param +from corelay.processor.base import Processor class Distance(Processor): - """Distance Processor + """The abstract base class for distance processors. + Args: + is_output (bool, optional): A value indicating whether this ``Distance`` processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. """ +PairWiseDistanceMeasure = Literal[ + 'braycurtis', + 'canberra', + 'chebychev', 'chebyshev', 'cheby', 'cheb', 'ch', + 'cityblock', 'cblock', 'cb', 'c', + 'correlation', 'co', + 'cosine', 'cos', + 'dice', + 'euclidean', 'euclid', 'eu', 'e', + 'hamming', 'hamm', 'ha', 'h', + 'minkowski', 'mi', 'm', + 'pnorm', + 'jaccard', 'jacc', 'ja', 'j', + 'jensenshannon', 'js', + 'kulczynski1', + 'mahalanobis', 'mahal', 'mah', + 'rogerstanimoto', + 'russellrao', + 'seuclidean', 'se', 's', + 'sokalmichener', + 'sokalsneath', + 'sqeuclidean', 'sqe', 'sqeuclid', + 'yule' +] +"""An enumeration of the distance measures supported by ``scipy.spatial.distance.pdist``.""" + + class SciPyPDist(Distance): - """Pairwise distances using scipy.spatial.distance.pdist + """A distance metric, that computes the pair-wise distance between observations in n-dimensional space using ``scipy.spatial.distance.pdist``. - Note - ---- - See scipy.spatial.distance.pdist for valid values of metric + Args: + is_output (bool, optional): A value indicating whether this ``SciPyPDist`` distance processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + metric (str): The distance metric to use. Default is "euclidean". + m_kwargs (dict): Additional keyword arguments to pass to the distance function. """ - metric = Param(str, 'euclidean', identifier=True) - m_args = Param(list, []) - m_kwargs = Param(dict, {}) - def function(self, data): - # pylint: disable=not-an-iterable,not-a-mapping - return squareform(pdist(data, metric=self.metric, *self.m_args, **self.m_kwargs)) + metric: Annotated[PairWiseDistanceMeasure, Param(str, 'euclidean', identifier=True)] + """The distance metric to use. Defaults to "euclidean".""" + + m_kwargs: Annotated[dict[str, Any], Param(dict, {})] + """Additional keyword arguments to pass to the distance function.""" + + def function(self, data: Any) -> Any: + """Applies the pairwise distance function to the input data. + + Args: + data (Any): The input data that is to be processed. The input data should be a NumPy array of shape + `(number_of_samples, number_of_features)`. + + Returns: + Any: Returns the pairwise distance matrix of shape `(number_of_samples, number_of_samples)`. + """ + + input_data: NDArray[Any] = data + distance_matrix: NDArray[Any] = pdist(input_data, metric=self.metric, **self.m_kwargs) + return squareform(distance_matrix) diff --git a/source/corelay/processor/embedding.py b/source/corelay/processor/embedding.py index 04418ba..4e30ba2 100644 --- a/source/corelay/processor/embedding.py +++ b/source/corelay/processor/embedding.py @@ -1,124 +1,326 @@ -"""Embedding Processors +"""A module that contains processors for embedding algorithms.""" -""" -import numpy as np +from collections.abc import Callable +from typing import Annotated, Any, Literal + +import numpy +from numpy.typing import NDArray from scipy.sparse.linalg import eigsh -from sklearn.manifold import TSNE, LocallyLinearEmbedding +from sklearn.base import TransformerMixin from sklearn.decomposition import PCA +from sklearn.manifold import TSNE, LocallyLinearEmbedding + +from corelay.base import Param +from corelay.processor.base import Processor +from corelay.processor.distance import PairWiseDistanceMeasure +from corelay.utils import import_or_stub + + +UMAP: Callable[..., TransformerMixin] = import_or_stub('umap', 'UMAP') +"""Performs the Uniform Manifold Approximation and Projection (UMAP) dimensionality reduction algorithm, which will find a low dimensional embedding +of the data that approximates an underlying manifold. -from ..utils import import_or_stub -from .base import Processor, Param -UMAP = import_or_stub('umap', 'UMAP') +Returns: + TransformerMixin: Returns a UMAP cluster estimator, which can be used to fit the data. + +Note: + Since the UMAP library is an optional dependency of CoRelAy, it is imported using the ``import_or_stub`` function, which tries to import the + module/type/function specified. If the import fails, it returns a stub instead, which will raise an exception when used. The exception message + will tell users how to install the missing dependencies for the functionality to work. +""" class Embedding(Processor): - """Embedding Processor base class + """The abstract base class for embedding processors. + Args: + is_output (bool, optional): A value indicating whether this ``Embedding`` processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the embedding algorithm. Defaults to an empty dictionary. """ - kwargs = Param(dict, {}) + + kwargs: Annotated[dict[str, Any], Param(dict, {})] + """Additional keyword arguments to pass to the embedding function.""" + + +EigenvalueType = Literal['LM', 'SM', 'LA', 'SA', 'BE'] +"""The type of eigenvalues and eigenvectors to compute. The options are: + +- "LM": Largest (in magnitude) eigenvalues. +- "SM": Smallest (in magnitude) eigenvalues. +- "LA": Largest (algebraic) eigenvalues. +- "SA": Smallest (algebraic) eigenvalues. +- "BE": Half (k/2) from each end of the spectrum. + +Note: + If the input is a complex Hermitian matrix, 'BE' is invalid. +""" class EigenDecomposition(Embedding): - """Eigenvalue Decomposition + """A spectral embedding ``Processor`` that performs eigenvalue decomposition. + Args: + is_output (bool, optional): A value indicating whether this ``EigenDecomposition`` embedding processor is the output of a ``Pipeline``. + Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the eigenvalue decomposition embedding algorithm. Defaults to an empty + n_eigval (int, optional): The number of eigenvalues and eigenvectors to compute. Defaults to 32. + which (str, optional): The type of eigenvalues and eigenvectors to compute. Defaults to "LM" (largest in magnitude). + normalize (bool, optional): A value indicating whether to normalize the eigenvectors. Defaults to `True`. + dictionary. """ - n_eigval = Param(int, 32, identifier=True) - which = Param(str, 'LM') - normalize = Param(bool, True, identifier=True) + + n_eigval: Annotated[int, Param(int, 32, identifier=True)] + """The number of eigenvalues and eigenvectors to compute. Defaults to 32.""" + + which: Annotated[EigenvalueType, Param(str, 'LM')] + """The type of eigenvalues and eigenvectors to compute. Defaults to "LM" (largest in magnitude).""" + + normalize: Annotated[bool, Param(bool, True, identifier=True)] + """A value indicating whether to normalize the eigenvectors. Defaults to True.""" @property - def _output_repr(self): - return '(eigval:np.ndarray, eigvec:np.ndarray)' + def _output_repr(self) -> str: + """Gets a string representation of the output of the function. - def function(self, data): - """Compute spectral embedding of `data` + Returns: + str: Returns a string representation of the output of the function. + """ + + return '(eigval: numpy.ndarray, eigvec: numpy.ndarray)' - Parameters - ---------- - data : :obj:`numpy.ndarray` - data with samples in rows + def function(self, data: Any) -> Any: + """Computes the spectral embedding of the input data using eigenvalue decomposition. - Returns - ------- - :obj:`numpy.ndarray` - Eigenvalues for spectral embedding - :obj:`numpy.ndarray` - Spectral embedding (eigenvectors) + Args: + data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. - Note - ---- - We use the fact that (I-A)v = (1-λ)v and thus compute the largest eigenvalues of the identity minus the - data and return one minus the eigenvalue. + Returns: + Any: Returns a tuple containing the eigenvalues and eigenvectors of the input data. + Note: + We use the fact that (I-A)v = (1-λ)v and thus compute the largest eigenvalues of the identity minus the data and return one minus the + eigenvalue. """ - # pylint: disable=not-a-mapping - eigval, eigvec = eigsh(data, k=self.n_eigval, which=self.which, **self.kwargs) - eigval = 1. - eigval + + input_data: NDArray[Any] = data + eigenvalues: NDArray[numpy.float64] + eigenvectors: NDArray[numpy.float64] + eigenvalues, eigenvectors = eigsh(input_data, k=self.n_eigval, which=self.which, **self.kwargs) # type: ignore[arg-type] + eigenvalues = 1.0 - eigenvalues if self.normalize: - eigvec /= np.linalg.norm(eigvec, axis=1, keepdims=True) - return eigval, eigvec + eigenvectors /= numpy.linalg.norm(eigenvectors, axis=1, keepdims=True) + + return eigenvalues, eigenvectors class TSNEEmbedding(Embedding): - """TSNE Embedding + """An embedding ``Processor`` that uses the t-SNE algorithm to reduce the dimensionality of the input data. + Args: + is_output (bool, optional): A value indicating whether this ``TSNEEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the t-SNE embedding algorithm. Defaults to an empty dictionary. + n_components (int, optional): The number of dimensions to reduce the data to. Defaults to 2. + metric (str, optional): The distance metric to use. Defaults to "euclidean". + perplexity (float, optional): The perplexity parameter for the t-SNE algorithm. Defaults to 30. + early_exaggeration (float, optional): The early exaggeration parameter for the t-SNE algorithm. Defaults to 12. """ - n_components = Param(int, default=2, identifier=True) - metric = Param(str, default='euclidean', identifier=True) - perplexity = Param(float, default=30., identifier=True) - early_exaggeration = Param(float, default=12., identifier=True) - - def function(self, data): - # pylint: disable=not-a-mapping - tsne = TSNE(n_components=self.n_components, - init='random', - metric=self.metric, - perplexity=self.perplexity, - early_exaggeration=self.early_exaggeration, - **self.kwargs) - emb = tsne.fit_transform(data) - return emb + + n_components: Annotated[int, Param(int, default=2, identifier=True)] + """The number of dimensions to reduce the data to. Defaults to 2.""" + + metric: Annotated[PairWiseDistanceMeasure, Param(str, default='euclidean', identifier=True)] + """The distance metric to use. Defaults to "euclidean".""" + + perplexity: Annotated[float, Param(float, default=30.0, identifier=True)] + """The perplexity parameter for the t-SNE algorithm. Defaults to 30.""" + + early_exaggeration: Annotated[float, Param(float, default=12.0, identifier=True)] + """The early exaggeration parameter for the t-SNE algorithm. Defaults to 12.""" + + def function(self, data: Any) -> Any: + """Computes the t-SNE embedding of the input data. + + Args: + data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or + `(number_of_samples, number_of_samples)`. + + Returns: + Any: Returns the t-SNE embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. + """ + + tsne = TSNE( + n_components=self.n_components, + init='random', + metric=self.metric, + perplexity=self.perplexity, + early_exaggeration=self.early_exaggeration, + **self.kwargs + ) + return tsne.fit_transform(data) class PCAEmbedding(Embedding): - """PCA Embedding + """An embedding ``Processor`` that uses the principal component analysis (PCA) algorithm to reduce the dimensionality of the input data. + Args: + is_output (bool, optional): A value indicating whether this ``PCAEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the PCA embedding algorithm. Defaults to an empty dictionary. + n_components (int, optional): The number of dimensions to reduce the data to. Defaults to 2. + whiten (bool, optional): A value indicating whether to whiten the data. Defaults to `False`. """ - n_components = Param(int, default=2, identifier=True) - whiten = Param(bool, default=False, identifier=True) - def function(self, data): - # pylint: disable=not-a-mapping - pca = PCA(n_components=self.n_components, whiten=self.whiten, **self.kwargs) - emb = pca.fit_transform(data) - return emb + n_components: Annotated[int, Param(int, default=2, identifier=True)] + """The number of dimensions to reduce the data to. Defaults to 2.""" + + whiten: Annotated[bool, Param(bool, default=False, identifier=True)] + """A value indicating whether to whiten the data. Defaults to `False`.""" + + def function(self, data: Any) -> Any: + """Computes the PCA embedding of the input data. + + Args: + data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + + Returns: + Any: Returns the PCA embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. + """ + + pca = PCA( + n_components=self.n_components, + whiten=self.whiten, + **self.kwargs + ) + return pca.fit_transform(data) class LLEEmbedding(Embedding): - """LocallyLinearEmbedding + """An embedding ``Processor`` that uses the locally linear embedding (LLE) algorithm to reduce the dimensionality of the input data. + Args: + is_output (bool, optional): A value indicating whether this ``LLEEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the LLE embedding algorithm. Defaults to an empty dictionary. + n_components (int, optional): The number of dimensions to reduce the data to. Defaults to 2. + n_neighbors (int, optional): The number of neighbors to use for the LLE algorithm. Defaults to 5. """ - n_components = Param(int, default=2, identifier=True) - n_neighbors = Param(int, default=5, identifier=True) - def function(self, data): - # pylint: disable=not-a-mapping - lle = LocallyLinearEmbedding(n_neighbors=self.n_neighbors, n_components=self.n_components, - **self.kwargs) - emb = lle.fit_transform(data) - return emb + n_components: Annotated[int, Param(int, default=2, identifier=True)] + """The number of dimensions to reduce the data to. Defaults to 2.""" + + n_neighbors: Annotated[int, Param(int, default=5, identifier=True)] + """The number of neighbors to use for the LLE algorithm. Defaults to 5.""" + + def function(self, data: Any) -> Any: + """Computes the LLE embedding of the input data. + + Args: + data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + + Returns: + Any: Returns the LLE embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. + """ + + lle = LocallyLinearEmbedding( + n_neighbors=self.n_neighbors, + n_components=self.n_components, + **self.kwargs + ) + return lle.fit_transform(data) + + +UMAPDistanceMetric = Literal[ + 'euclidean', + 'manhattan', + 'chebyshev', + 'minkowski', + 'canberra', + 'braycurtis', + 'mahalanobis', + 'wminkowski', + 'seuclidean', + 'cosine', + 'correlation', + 'haversine', + 'hamming', + 'jaccard', + 'dice', + 'russelrao', + 'kulsinski', + 'll_dirichlet', + 'hellinger', + 'rogerstanimoto', + 'sokalmichener', + 'sokalsneath', + 'yule' +] +"""An enumeration of the distance measures supported by ``umap.UMAP``.""" class UMAPEmbedding(Embedding): - """UMAPEmbedding: https://umap-learn.readthedocs.io/en/latest/index.html + """An embedding ``Processor`` that uses the Uniform Manifold Approximation and Projection (UMAP) algorithm to reduce the dimensionality of the + input data. + Args: + is_output (bool, optional): A value indicating whether this ``UMAPEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict[str, Any], optional): Additional keyword arguments for the UMAP embedding algorithm. Defaults to an empty dictionary. + n_neighbors (int, optional): The number of neighbors to use for the UMAP algorithm. Defaults to 15. + min_dist (float, optional): The minimum distance between points in the UMAP algorithm. Defaults to 0.1. + metric (str, optional): The distance metric to use for the UMAP algorithm. Defaults to "correlation". """ - n_neighbors = Param(int, default=15, identifier=True) - min_dist = Param(float, default=0.1, identifier=True) - metric = Param(str, default='correlation', identifier=True) - - def function(self, data): - umap = UMAP(n_neighbors=self.n_neighbors, min_dist=self.min_dist, metric=self.metric) - emb = umap.fit_transform(data) - return emb + + n_neighbors: Annotated[int, Param(int, default=15, identifier=True)] + """The number of neighbors to use for the UMAP algorithm. Defaults to 15.""" + + min_dist: Annotated[float, Param(float, default=0.1, identifier=True)] + """The minimum distance between points in the UMAP algorithm. Defaults to 0.1.""" + + metric: Annotated[UMAPDistanceMetric, Param(str, default='correlation', identifier=True)] + """The distance metric to use for the UMAP algorithm. Defaults to "correlation".""" + + def function(self, data: Any) -> Any: + """Computes the UMAP embedding of the input data. + + Args: + data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + + Returns: + Any: Returns the UMAP embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_new_features)`. + + Note: + For information on the UMAP algorithm, see the `UMAP documentation `_. + """ + + umap = UMAP( + n_neighbors=self.n_neighbors, + min_dist=self.min_dist, + metric=self.metric + ) + return umap.fit_transform(data) diff --git a/source/corelay/processor/flow.py b/source/corelay/processor/flow.py index 9a41436..3f7a4b5 100644 --- a/source/corelay/processor/flow.py +++ b/source/corelay/processor/flow.py @@ -1,148 +1,218 @@ -"""Basic flow operation Processors, such as Shaper, Sequential and Parallel""" -from ..base import Param -from .base import Processor -from ..utils import zip_equal, Iterable +"""A module that contains basic flow operation processors, such as ``Shaper``, ``Sequential`` and ``Parallel``.""" +from collections.abc import Iterable, Iterator, Sequence +from typing import Annotated, Any + +from corelay.base import Param +from corelay.processor.base import Processor +from corelay.utils import zip_equal -class Shaper(Processor): - """Extracts and/ or copies by indices. - Attributes - ---------- - indices : iterable of (int or iterable of int) - Iterable of indices to copy/ extract. The resulting output will be a tuple with the same member shape. Each index - may be passed an arbitrary amount of times. Outer tuples allow ints and tuples, inner tuples only allow ints. +RecursiveIndicesTuple = tuple['int | RecursiveIndicesTuple', ...] +"""A recursive tuple of integer indices, i.e., a tuple that contains integer indices or other tuples of integer indices, which themselves can contain +other tuples of integer indices, and so on. This is used to represent a nested structure of integer indices. +""" - Examples - ------- - >>> Shaper(indices=(0, 1, (0, 1, 2)))(['a', 'b', 'c']) - ('a', 'b', ('a', 'b', 'c')) +class Shaper(Processor): + """Extracts and/or copies by indices. + + Args: + is_output (bool, optional): A value indicating whether this ``Shaper`` processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + indices (RecursiveIndicesTuple): The indices to copy/extract. The resulting output will be a tuple with the same member shape. Each index may + be passed an arbitrary amount of times. Outer tuples allow integers and tuples, inner tuples only allow integers. + + Examples: + >>> Shaper(indices=(0, 1, (0, 1, 2)))(['a', 'b', 'c']) + ('a', 'b', ('a', 'b', 'c')) """ - indices = Param(Iterable[int, Iterable[int]], positional=True) - def function(self, data): - """Extracts and/ or copies indices of data. + indices: Annotated[RecursiveIndicesTuple, Param(tuple, positional=True)] + """The indices to copy/extract. The resulting output will be a tuple with the same member shape. Each index may be passed an arbitrary amount of + times. Outer tuples allow integers and tuples, inner tuples only allow integers. + """ - Parameters - ---------- - data : object - Object from which to extract/ copy elements. If not an iterable, index 0 corresponds to the object itself. + def function(self, data: Any) -> Any: + """Extracts and/or copies indices of data. - Returns - ------- - tuple of object - The extracted/ copied indices of data. + Args: + data (Any): The data from which the elements, identified by the indices, are to be extracted. This can be any object, but if it is not an + iterable, index 0 corresponds to the object itself. - Raises - ------ - TypeError - If an invalid index was accessed for data. + Raises: + TypeError: An invalid index was accessed in the data. + Returns: + Any: Returns the extracted/copied elements of the data, identified by the indices. The output is a tuple with the same member shape as the + indices. """ - def extract_indices(iterable, indices): - """Recursively extract indices""" - result = [] + + def extract_elements_from_indices(iterable: Sequence[Any], indices: RecursiveIndicesTuple) -> tuple[Any, ...]: + """Recursively extracts the elements of the specified ``iterable`` based on the specified ``indices``. + + Args: + iterable (Sequence[Any]): The iterable from which to extract the elements. + indices (RecursiveIndicesTuple): The indices to extract. This can be a tuple of integers or other tuples of integers. + + Raises: + TypeError: An invalid index was accessed in the iterable. + + Returns: + tuple[Any, ...]: The extracted elements as a tuple. The output is a tuple with the same member shape as the indices. + """ + + results = [] for index in indices: if isinstance(index, Iterable): - obj = extract_indices(iterable, index) + extracted_element = extract_elements_from_indices(iterable, index) else: try: - obj = data[index] + extracted_element = iterable[index] except KeyError as err: - raise TypeError(f"'{index}' is not a valid index for '{data}'") from err - result.append(obj) - return tuple(result) + raise TypeError(f'The "{index}" is not a valid index for "{iterable}".') from err + results.append(extracted_element) + return tuple(results) - if not isinstance(data, Iterable): + if not isinstance(data, Sequence): data = (data,) - result = extract_indices(data, self.indices) - return result + try: + return extract_elements_from_indices(data, self.indices) + except TypeError as exception: + raise TypeError('An invalid index was used to index the input data.') from exception class GroupProcessor(Processor): - """Abstract class for groups of Processors. - - Attributes - ---------- - children : iterable of :obj:`Processor` - Child Processors for this group. - + """The abstract base class for groups of processors. + + Args: + is_output (bool, optional): A value indicating whether this ``GroupProcessor`` processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + children (Iterable[Processor]): The children of the group. This is a list of processors that will be called in parallel or sequentially. """ - children = Param(Iterable[Processor], positional=True) + children: Annotated[Iterable[Processor], Param(Iterable, positional=True)] + """The children of the group. This is a list of processors that will be called in parallel or sequentially.""" -class Parallel(GroupProcessor): - """Processor group calling its children in Parallel. - Examples - -------- - >>> Parallel(children=[FunctionProcessor(function=lambda x: x**n) for n in (1, 2, 3, 4)])((2, 2, 2, 2)) - [2, 4, 8, 16] - >>> Parallel(children=[FunctionProcessor(function=lambda x: x**n) for n in (1, 2, 3, 4)])(2) - [2, 4, 8, 16] +class Parallel(GroupProcessor): + """A processor group that is invoking its children in parallel. + + Note: + Please note, that the child processors are not executed in parallel in the sense of multiprocessing, but that the children all either receive + the same input data or an element of the input data, in contrast to the ``Sequential`` processor group, which first executes the first child + and then feeds the output to the next child. + + Args: + is_output (bool, optional): A value indicating whether this ``Parallel`` group processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + children (Iterable[Processor]): The children of the group. This is a list of processors that will be called in parallel. + broadcast (bool, optional): A value indicating whether the input data should be broadcasted to all children. If ``True``, the input data will + be copied as many times as there are children. If ``False`` and the input is an iterable, the elements of the iterable will be passed to + the children one by one. Defaults to `False`. + + Examples: + >>> Parallel(children=[FunctionProcessor(processing_function=lambda x: x**n) for n in (1, 2, 3, 4)])((2, 2, 2, 2)) + [2, 4, 8, 16] + >>> Parallel(children=[FunctionProcessor(processing_function=lambda x: x**n) for n in (1, 2, 3, 4)])(2) + [2, 4, 8, 16] + """ + broadcast: Annotated[bool, Param(bool, False)] + """A value indicating whether the input data should be broadcasted to all children. If ``True``, the input data will be copied as many times as + there are children. If ``False`` and the input is an iterable, the elements of the iterable will be passed to the children one by one. Defaults to + `False`. """ - broadcast = Param(bool, False) - def function(self, data): - """Sequentially get one element from data per child, call all children with this element as input in parallel, - and accumulate the outputs. + def function(self, data: Any) -> Any: + """Invokes the children in parallel, passing the input data to each child. If ``broadcast`` is ``True``, the input data will be copied as many + times as there are children. If ``broadcast`` is ``False`` and the input is an iterable, the elements of the iterable will be passed to the + children one by one. - Parameters - ---------- - data : iterable or object - Iterable from which to pass the elements to the children. If data is not an Iterable, it will be copied as - many times as there are children. + Args: + data (Any): The input data to pass to the children. If ``broadcast`` is ``True``, this can be any object. If ``broadcast`` is ``False``, + and the input is an iterable, the elements of the iterable will be passed to the children one by one. - Raises - ------ - TypeError - If the number of children and number of data elements mismatch. + Raises: + TypeError: The ``broadcast`` parameter is set to `True`, and the number of children and number of data elements mismatch. + Returns: + Any: Returns a tuple that has the same number of elements as there are children and contains the outputs of the child processors. """ - def wrap_err(iterable): - """Wraps around iterator to raise correct error""" + + def wrap_iterator_with_meaningful_exception(iterable: Iterable[Any]) -> Iterator[Any]: + """An iterator that wraps the passed iterator and raises an error, if the number of elements that the iterator produces is not the same as + the number of child processors. This is done so that the error message is more informative. + + Args: + iterable (Iterable[Any]): The iterator to wrap. + + Yields: + Any: The elements of the iterator. + + Raises: + TypeError: The number of elements that the iterator produces is not the same as the number of child processors. + """ + iterator = iter(iterable) while True: try: yield next(iterator) - except TypeError as err: - raise TypeError("Number of data elements and children does not match!") from err + except TypeError as exception: + raise TypeError('Number of data elements and children does not match.') from exception except StopIteration: break if not isinstance(data, Iterable) or self.broadcast: - # pylint: disable=not-an-iterable data = tuple(data for _ in self.children) - result = [] - for child, element in wrap_err(zip_equal(self.children, data)): - out = child(element) - result.append(out) - return tuple(result) + try: + results = [] + for child, element in wrap_iterator_with_meaningful_exception(zip_equal(self.children, data)): + output = child(element) + results.append(output) + return tuple(results) + except TypeError as exception: + raise TypeError('Number of data elements and children does not match.') from exception class Sequential(GroupProcessor): - """Processor group calling its children in Sequence, feeding the input the first child, and then each output to the - next child. - - Examples - -------- - >>> Sequential(children=[FunctionProcessor(function=lambda x: c + x) for c in 'abcd'])('=') - 'dcba=' - + """A processor group that invokes its children in sequence, feeding the input the first child, and then each output to the next child. + + Args: + is_output (bool, optional): A value indicating whether this ``Sequential`` group processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + children (Iterable[Processor]): The children of the group. This is a list of processors that will be called in sequentially. + + Examples: + >>> Sequential(children=[FunctionProcessor(processing_function=lambda x: c + x) for c in 'abcd'])('=') + 'dcba=' """ - def function(self, data): - """Feed data forward sequentially, passing each child's output to the next child. - Parameters - ---------- - data : object - Input data to pass to the first child. + def function(self, data: Any) -> Any: + """Invokes the child processors of the processor group in sequence. The input data is fed to the first child, whose output is then fed into + the second child, and so on. + Args: + data (Any): The input data to pass to the first child. + + Returns: + Any: Returns the output of the last child processor. """ - # pylint: disable=not-an-iterable + for child in self.children: data = child(data) return data diff --git a/source/corelay/processor/laplacian.py b/source/corelay/processor/laplacian.py index a2c924c..153315f 100644 --- a/source/corelay/processor/laplacian.py +++ b/source/corelay/processor/laplacian.py @@ -1,80 +1,88 @@ -"""Graph Laplacian Processors (mainly for spectral embedding) +"""A module that contains processors for computing the graph Laplacian, which is mainly used for spectral embeddings.""" -""" -import logging +from typing import Any -import numpy as np -from scipy import sparse as sp +import numpy +import scipy.sparse +from numpy.typing import NDArray -from .base import Processor +from corelay.processor.base import Processor -LOGGER = logging.getLogger(__name__) +def a1ifmat(matrix: NDArray[Any] | numpy.matrix[Any, Any]) -> NDArray[Any]: + """Converts the specified matrix to a flat representation. If the matrix is already a flat NumPy array, it is returned as is. -def a1ifmat(x): - """Return flat representation of x if x is a :obj:`numpy.matrix` - - Parameters - ---------- - x : :obj:`numpy.ndarray` or :obj:`numpy.matrix` - Object to convert if necessary - - Returns - ------- - :obj:`numpy.ndarray` - Matrix as flat :obj:`numpy.ndarray` if `x` was a :obj:`numpy.matrix`, else `x` + Args: + matrix (NDArray[Any] | numpy.matrix[Any, Any]): The input matrix to be converted. + Returns: + NDArray[Any]: The converted matrix. If the input was a NumPy matrix, it is returned as a flat NumPy array. Otherwise, the input is returned as + is. """ - return x.A1 if isinstance(x, np.matrix) else x + return matrix.A1 if isinstance(matrix, numpy.matrix) else matrix -class Laplacian(Processor): - """Graph Laplacian Processor +class Laplacian(Processor): + """The abstract base class for processors that compute a graph Laplacian. + + Args: + is_output (bool, optional): A value indicating whether this ``Laplacian`` processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. """ class SymmetricNormalLaplacian(Laplacian): - """ Normal Symmetric Graph Laplacian - + """A ``Processor`` that computes the normal symmetric graph Laplacian. + + Args: + is_output (bool, optional): A value indicating whether this ``SymmetricNormalLaplacian`` Laplacian processor is the output of a ``Pipeline``. + Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. """ - def function(self, data): - """Normalized Symmetric Graph Laplacian - Parameters - ---------- - data : :obj:`sp.csr_matrix` or :obj:`np.ndarray` - Graph affinity/similarity matrix. + def function(self, data: Any) -> Any: + """Computes the symmetric normal graph Laplacian. - Returns - ------- - :obj:`sp.csr_matrix` - Sparse representation of a symmetric graph laplacian matrix + Args: + data (Any): The graph affinity/similarity matrix. This can be a NumPy array or a sparse matrix. + Returns: + Any: Returns the symmetric normal graph Laplacian, which is a sparse representation of the symmetric graph Laplacian matrix. """ - deg = sp.diags(a1ifmat(data.sum(1))**-.5, 0) - lap = deg @ data @ deg - return lap + input_data: scipy.sparse.csr_matrix | NDArray[Any] = data + degree = scipy.sparse.diags(a1ifmat(input_data.sum(1))**-0.5, 0) + return degree @ input_data @ degree -class RandomWalkNormalLaplacian(Laplacian): - """ Normal Random Walk Graph Laplacian +class RandomWalkNormalLaplacian(Laplacian): + """A ``Processor`` that computes the normal random walk graph Laplacian. + + Args: + is_output (bool, optional): A value indicating whether this ``RandomWalkNormalLaplacian`` Laplacian processor is the output of a ``Pipeline``. + Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. """ - def function(self, data): - """Normalized Random Walk Graph Laplacian - Parameters - ---------- - affinity : :obj:`sp.csr_matrix` or :obj:`np.ndarray` - Graph affinity/similarity matrix. + def function(self, data: Any) -> Any: + """Computes the random walk normal graph Laplacian. - Returns - ------- - :obj:`sp.csr_matrix` - Sparse representation of a random walk graph laplacian matrix + Args: + data (Any): The graph affinity/similarity matrix. This can be a NumPy array or a sparse matrix. + Returns: + Any: Returns the random walk normal graph Laplacian, which is a sparse representation of the random walk graph Laplacian matrix. """ - deg = sp.diags(a1ifmat(data.sum(1))**-1., 0) - lap = deg @ data - return lap + + degree = scipy.sparse.diags(a1ifmat(data.sum(1))**-1., 0) + return degree @ data diff --git a/source/corelay/processor/preprocessing.py b/source/corelay/processor/preprocessing.py index 65a63fc..354d506 100644 --- a/source/corelay/processor/preprocessing.py +++ b/source/corelay/processor/preprocessing.py @@ -1,191 +1,259 @@ -"""Preprocessing Processors +"""A module that contains processors for pre-processing data.""" -""" from types import FunctionType +from typing import Annotated, Any -import numpy as np -import skimage.transform +import numpy import skimage.measure +import skimage.transform +from numpy.typing import NDArray -from .base import Processor, Param +from corelay.base import Param +from corelay.processor.base import Processor class PreProcessor(Processor): - """Base-class for preprocessing. - + """The abstract base class for pre-processing processors. + + Args: + is_output (bool, optional): A value indicating whether this ``PreProcessor`` is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict, optional): Additional keyword arguments to pass to the pre-processing function. Defaults to an empty dictionary. """ - kwargs = Param(dict, {}) + kwargs: Annotated[dict[str, Any], Param(dict, {})] + """Additional keyword arguments to pass to the pre-processing function.""" -class Histogram(PreProcessor): - """Channel-wise histograms of data - - Parameters - ---------- - bins : int - number of bins for histograms +class Histogram(PreProcessor): + """A ``Processor`` that computes channel-wise histograms of the input data. + + Args: + is_output (bool, optional): A value indicating whether this ``Histogram`` pre-processor is the output of a ``Pipeline``. Defaults to `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict, optional): Additional keyword arguments to pass to the pre-processing function. Defaults to an empty dictionary. + bins (int, optional): The number of bins for the histogram. Defaults to 32. """ - bins = Param(int, 32) - def function(self, data): - """Compute channel-wise histograms from data + bins: Annotated[int, Param(int, 32)] + """Number of bins for the histogram. Defaults to 32.""" - Parameters - ---------- - data : :obj:`numpy.ndarray` - image data with shape samples x channels x height x width + def function(self, data: Any) -> Any: + """Computes channel-wise histograms from the input data. - Returns - ------- - :obj:`numpy.ndarray` - Channel-wise histograms with shape samples x channels x bins + Args: + data (Any): The input data to compute histograms for, which is should be an image as a NumPy array of shape + `(number_of_samples, number_of_channels, height, width)`. + Returns: + Any: Returns a NumPy array, which contains the channel-wise histograms of the input data of shape + `(number_of_samples, number_of_channels, bins)`. """ - n, c, h, w = data.shape - # channel-wise range - trange = zip(data.min((0, 2, 3), data.max(0, 2, 3))) - # pylint: disable=no-member - hist = np.histogramdd(data.reshape(n * c, h * w), - bins=self.bins, range=trange, density=True).reshape(n, c, self._bins) - return hist + + input_data: NDArray[Any] = data + number_of_samples, number_of_channels, height, width = input_data.shape + + channel_minima: NDArray[numpy.float64] = input_data.min((0, 2, 3)) + channel_maxima: NDArray[numpy.float64] = input_data.max((0, 2, 3)) + channel_range = list(zip(channel_minima, channel_maxima)) + + histogram, _ = numpy.histogramdd( + input_data.reshape(number_of_samples * number_of_channels, height * width), + bins=self.bins, + range=channel_range, + density=True + ) + return histogram.reshape(number_of_samples, number_of_channels, self.bins) class ImagePreProcessor(PreProcessor): - """Abstract PreProcessor for Images. - - Parameters - ---------- - filter : int - The order of interpolation. The order has to be in the range 0-5: - - 0: Nearest-neighbor - - 1: Bi-linear (default) - - 2: Bi-quadratic - - 3: Bi-cubic - - 4: Bi-quartic - - 5: Bi-quintic - channels_first: bool - True if channels first else False. + """The abstract base class for all processors that perform pre-processing on images. + + Args: + is_output (bool, optional): A value indicating whether this ``ImagePreProcessor`` pre-processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. + filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. """ - filter = Param(int, 1) - channels_first = Param(bool, True) + filter: Annotated[int, Param(int, 1)] + """The order of interpolation. The order has to be in the range 0-5: -class Resize(ImagePreProcessor): - """Resize images. - - Parameters - ---------- - width : int - Width of resized images. Default: 100 - height: int - Height of resized images. Default: 100 - filter : int - The order of interpolation. The order has to be in the range 0-5: - - 0: Nearest-neighbor - - 1: Bi-linear (default) - - 2: Bi-quadratic - - 3: Bi-cubic - - 4: Bi-quartic - - 5: Bi-quintic - channels_first: bool - True if channels first else False. - kwargs: dict - Check skimage.transform.resize. + - 0: Nearest-neighbor + - 1: Bi-linear (default) + - 2: Bi-quadratic + - 3: Bi-cubic + - 4: Bi-quartic + - 5: Bi-quintic + Defaults to 1 (bi-linear). """ - width = Param(int, 100) - height = Param(int, 100) - def function(self, data): - """ - Parameters - ---------- - data: np.ndarray - Shape of data should be in one of the following formats: - 1. (batch_size, channels, height, width) with channels_first=True - 2. (batch_size, height, width, channels) with channels_first=False - 3. (batch_size, height, width) with channels_first=False - Returns - ------- - data: np.ndarray - Data is resized to given width and height. + channels_first: Annotated[bool, Param(bool, True)] + """A value indicating whether the input data is in channels-first format or not. Defaults to `True`.""" + + +class Resize(ImagePreProcessor): + """A ``Processor`` that resizes images to a specified width and height. + + Args: + is_output (bool, optional): A value indicating whether this ``Resize`` image pre-processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. + filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. + width (int, optional): The width to which the images are resized. Defaults to 100. + height (int, optional): The height to which the images are resized. Defaults to 100. + """ + + width: Annotated[int, Param(int, 100)] + """The width to which the images are resized. Defaults to 100.""" + + height: Annotated[int, Param(int, 100)] + """The height to which the images are resized. Defaults to 100.""" + + def function(self, data: Any) -> Any: + """Resizes the input images to the specified width and height. + + Args: + data (Any): The input data, which contains the images that are to be resized. The input data should be a NumPy array in one of the + following formats: + + 1. `(batch_size, number_of_channels, height, width)`, if ``channels_first`` is set to `True`. + 2. `(batch_size, height, width, number_of_channels)`, if ``channels_first`` is set to `False`. + 3. `(batch_size, height, width)`, if ``channels_first`` is set to `False`. + + Returns: + Any: Returns a NumPy array containing the resized images, with a shape that matches the input data format. """ + + input_images: NDArray[Any] = data + if self.channels_first: - data = np.moveaxis(data, 1, -1) - # pylint: disable=not-a-mapping - out = np.stack([skimage.transform.resize(x, output_shape=(self.height, self.width), order=self.filter, - **self.kwargs) - for x in data]) + input_images = numpy.moveaxis(data, 1, -1) + + resized_images = numpy.stack([ + skimage.transform.resize( # type: ignore[no-untyped-call] + image, + output_shape=(self.height, self.width), + order=self.filter, + **self.kwargs + ) for image in input_images + ]) + if self.channels_first: - out = np.moveaxis(out, -1, 1) - return out + resized_images = numpy.moveaxis(resized_images, -1, 1) + return resized_images class Rescale(ImagePreProcessor): - """Rescale images. - - Parameters - ---------- - scale : float - Scale to which the images are rescaled. Default: 0.5 - filter : int - The order of interpolation. The order has to be in the range 0-5: - - 0: Nearest-neighbor - - 1: Bi-linear (default) - - 2: Bi-quadratic - - 3: Bi-cubic - - 4: Bi-quartic - - 5: Bi-quintic - channels_first: bool - True if channels first else False. - kwargs: dict - Check skimage.transform.rescale. - + """A ``Processors`` that rescales images by a specified scale factor. + + Args: + is_output (bool, optional): A value indicating whether this ``Rescale`` image pre-processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. + filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. + scale (float, optional): The scale factor by which the images are rescaled. Defaults to 0.5. """ - scale = Param(float, 0.5) - def function(self, data): - """ - Parameters - ---------- - data: np.ndarray - Shape of data should be in one of the following formats: - 1. (batch_size, channels, height, width) with channels_first=True - 2. (batch_size, height, width, channels) with channels_first=False - 3. (batch_size, height, width) with channels_first=False - Returns - ------- - data: np.ndarray - height and width axes are rescaled by scale parameter. + scale: Annotated[float, Param(float, 0.5)] + """The scale factor by which the images are rescaled. Defaults to 0.5.""" + + def function(self, data: Any) -> Any: + """Rescales the input images by the specified scale factor. + + Args: + data (Any): The input data, which contains the images that are to be rescaled. The input data should be a NumPy array in one of the + following formats: + + 1. `(batch_size, number_of_channels, height, width)`, if ``channels_first`` is set to `True`. + 2. `(batch_size, height, width, number_of_channels)`, if ``channels_first`` is set to `False`. + 3. `(batch_size, height, width) `, if ``channels_first`` is set to `False`. + + Returns: + Any: Returns a NumPy array containing the rescaled images, with a shape that matches the input data format. """ - multichannel = len(data.shape) > 3 + + input_data: NDArray[Any] = data + images_are_multi_channel = len(input_data.shape) > 3 + if self.channels_first: - data = np.moveaxis(data, 1, -1) - # pylint: disable=not-a-mapping - out = np.stack([skimage.transform.rescale(x, self.scale, order=self.filter, channel_axis=-1 if multichannel else None, - **self.kwargs) for x in data]) + input_data = numpy.moveaxis(input_data, 1, -1) + + rescaled_images = numpy.stack([ + skimage.transform.rescale( + image, + self.scale, + order=self.filter, + channel_axis=-1 if images_are_multi_channel else None, + **self.kwargs + ) for image in input_data + ]) + if self.channels_first: - out = np.moveaxis(out, -1, 1) - return out + rescaled_images = numpy.moveaxis(rescaled_images, -1, 1) + return rescaled_images class Pooling(PreProcessor): - """Preform pooling operation on given stride. + """A ``Processor`` that performs image pooling on the input data. + + Args: + is_output (bool, optional): A value indicating whether this ``Pooling`` image pre-processor is the output of a ``Pipeline``. Defaults to + `False`. + is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to `False`. + io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this + run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. + filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. + stride (tuple[int], optional): The pooling stride, which should be of shape `(batch_size, number_of_channels, height, width)`. Defaults to + `(1, 1, 2, 2)`. + pooling_function (FunctionType, optional): The pooling function to use to reduce the selected blocks. Defaults to ``numpy.sum``. + """ - Parameters - ---------- - stride : tuple[float] - The pooling stride. Default: (batch, channel, height, width) -> (1, 1, 2, 2) - pooling_function: FunctionType - Function that reduces the selected blocks. Default: np.sum - kwargs: dict - Check skimage.measure.block_reduce. + stride: Annotated[tuple[int], Param(tuple, (1, 1, 2, 2))] + """The pooling stride, which should be of shape `(batch_size, number_of_channels, height, width)`. Defaults to `(1, 1, 2, 2)`.""" - """ - stride = Param(tuple, (1, 1, 2, 2)) - pooling_function = Param(FunctionType, np.sum) + pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)] + """The pooling function to use to reduce the selected blocks. Defaults to ``numpy.sum``.""" + + def function(self, data: Any) -> Any: + """Performs pooling on the input data. + + Args: + data (Any): The input data, which should be a NumPy array of shape `(number_of_samples, number_of_channels, height, width)`. + + Returns: + Any: Returns a NumPy array containing the pooled data. + """ - def function(self, data): - # pylint: disable=not-a-mapping - return skimage.measure.block_reduce(data, self.stride, self.pooling_function, **self.kwargs) + input_data: NDArray[Any] = data + return skimage.measure.block_reduce( # type: ignore[no-untyped-call] + input_data, + self.stride, + self.pooling_function, + **self.kwargs + ) diff --git a/source/corelay/py.typed b/source/corelay/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/source/corelay/tracker.py b/source/corelay/tracker.py index 29fd67a..b5be8f6 100644 --- a/source/corelay/tracker.py +++ b/source/corelay/tracker.py @@ -1,111 +1,208 @@ """Includes MetaTracker to track definition order of class attributes.""" -from collections import OrderedDict + from abc import ABCMeta +from collections import OrderedDict +from typing import Any + + +class MetaTracker(ABCMeta): + """A meta class to track attributes of a type. + + Note: + This is used to track the slots of a class. In CoRelAy, slots are declared as class attributes, and during instantiation, the class attributes + are converted to respective instance attributes of the data type declared in the slot. For example, a ``Param`` slot is used to declare + parameters of processors. When a processor has a class attribute that is a ``Param`` with a data type of ``int``, then when the processor is + instantiated, an instance attribute of the same name is created with the data type of ``int``. There are two ways a slot can be declared: + + 1) The old way of declaring a slot, which is to declare it as a class attribute and assign it a ``Slot`` instance, e.g., + ``param = Param(int, 0)``. This is not the recommended way of declaring a slot anymore, because it causes problems with static type + checkers, like MyPy. In Python, class attributes can be accessed using the class or using the instance. For example, if a class ``Test`` + has a class attribute ``a = 5``, then ``Test.a`` and ``Test().a`` will both return ``5``. Static type checkers, like MyPy, do not know that + we are converting the class attribute ``a`` to an instance attribute of type ``int`` during instantiation, so they will assume that when a + slot is accessed using the instance, it will have the same type as the class attribute. This means that the static type checker will show + an error when the slot is accessed using the instance, because the type of the class attribute is ``Slot`` and not ``int``. + 2) The new way of declaring a slot, which is to declare it as a class attribute of type ``Annotated``. The ``Annotated`` type is a special + type that allows us to add metadata to a type hint. The first argument of ``Annotated`` is the actual type of the attribute, and then any + number of additional arguments can be passed, which are used as metadata. So, for example, a parameter slot can be declared as + ``param: Annotated[float, Param(float, 0.0)]``. Since the static type checker knows that the actual type of the attribute is ``float``, it + will not show an error when the slot is accessed using the instance. The metadata is used to store the instance of the slot, which contains + the information about the data type, the default value, etc. Unfortunately, this is only a declaration, which has no effect on the runtime. + This means, that no actual class attribute is created. But ``Annotated`` will add a special ``__annotations__`` attribute to the class, + which contains a dictionary with the names of the declared class attributes and their metadata. + + The ``MetaTracker`` meta class is used to track the class attributes of a class that are not special "dunder" attributes like ``__class__``, + as well as the declared class attributes from the ``__annotations__`` attribute. The tracked class attributes are stored in an ``OrderedDict`` + called ``__tracked__``. The ``Tracker`` class uses the ``MetaTracker`` meta class and allows users to access the tracked class attributes. The + class attributes are tracked in the order they were declared in the class, with the caveat that first, all class attributes come in the order + of declaration, and then all declared class attributes come in the order of declaration. As long as only one method of declaring a slot is + used, the order of declaration will be preserved. + + For more information on meta classes, please refer to `PEP 3115 `_. + + Example: + >>> class OrderedInts(metaclass=MetaTracker): + ... a = 14 + ... b = 21 + ... c = 42 + ... OrderedInts(a=0).__tracked__ + OrderedDict([('a', 0), ('b', 21), ('c', 42)]) + """ + + @classmethod + def __prepare__( # pylint: disable=unused-argument + mcs, + class_name: str, + base_classes: tuple[type, ...], + /, + **kwargs: Any + ) -> OrderedDict[str, Any]: + """Prepare the class dict to be an ``OrderedDict``. This is done to preserve the order of declaration of the class attributes. + + Args: + class_name (str): The name of the class. + base_classes (tuple[type, ...]): The base classes of the class. + **kwargs (Any): Additional keyword arguments. + + Returns: + OrderedDict[str, Any]: Returns a ``OrderedDict``, which will be used as the dictionary for the class attributes. + """ + return OrderedDict() -class PublicOrderedDict(OrderedDict): - """Supplies `public` function to return copy with only keys that are not inclosed in double underscores""" - def public(self): - """Returns copy with only keys that are not inclosed in double underscores""" - return OrderedDict((key, value) for key, value in self.items() if not (key[:2] + key[-2:]) == '____') + def __new__(mcs, class_name: str, base_classes: tuple[type, ...], class_attributes: OrderedDict[str, Any]) -> 'MetaTracker': + """Is called when a new class is created with the ``MetaTracker`` as its metaclass. Attaches a new ``__tracked__`` attribute to the class, + which is a dictionary with all public attributes of the class, i.e., those not enclosed in double underscores. If the class that is being + created already has a ``__tracked__`` attribute, the new attributes are appended to it. + Args: + class_name (str): The name of the class. + base_classes (tuple[type, ...]): The base classes of the class. + class_attributes (OrderedDict[str, Any]): A dictionary with the attributes of the class. In this case, with the addition of tracker + attributes. -class MetaTracker(ABCMeta): - """Meta class to track attributes of some type in order of declaration + Returns: + MetaTracker: Returns the new class with the ``__tracked__`` attribute. + """ - Example - ------- - >>> class OrderedInts(metaclass=MetaTracker): - ... a = 14 - ... b = 21 - ... c = 42 - ... OrderedInts(a=0).__tracked__ - OrderedDict([('__module__', '__main__'), ('__qualname__', 'OrderedInts'), ('a', 0), ('b', 21), ('c', 42)]) + # Retrieves the class attributes that are not special "dunder" attributes, like __class__, i.e., any class attributes that is not enclosed in + # double underscores (this is the classical way in which slots can be declared) + tracked_class_attributes: OrderedDict[str, Any] = OrderedDict( + (attribute_name, attribute_value) + for attribute_name, attribute_value in class_attributes.items() + if not (attribute_name[:2] + attribute_name[-2:]) == '____' + ) + + # Retrieves the declared class attributes, which were declared using Annotated (this is the new way in which slots can be declared) + tracked_declared_class_attributes: OrderedDict[str, Any] = OrderedDict() + if '__annotations__' in class_attributes: + for attribute_name, attribute_value in class_attributes['__annotations__'].items(): + if (attribute_name[:2] + attribute_name[-2:]) == '____': + continue + if not hasattr(attribute_value, '__metadata__'): + continue + if not isinstance(attribute_value.__metadata__, tuple) or len(attribute_value.__metadata__) == 0: + continue + tracked_declared_class_attributes[attribute_name] = attribute_value.__metadata__[0] + + # The way that slots work is, that they override the __get__, __set__, and __delete__ methods of the class, which are invoked when the class + # or instance attribute that the slot is assigned to is accessed (i.e., when the class or instance attribute is read, written, or deleted); + # for this reason the slots that were declared using Annotated have to be added as real class attributes to the class, otherwise, the user + # will not be able to access them + class_attributes.update(tracked_declared_class_attributes) + + # Creates a new class with the given name, bases, and class attributes + new_class: Any = super().__new__(mcs, class_name, base_classes, dict(class_attributes)) + + # Checks if the class or one of its base classes already has a __tracked__ attribute, if not, a new __tracked__ attribute is created, + # otherwise, the __tracked__ attribute is copied + tracked_attributes: OrderedDict[str, Any] = OrderedDict() + if hasattr(new_class, '__tracked__') and isinstance(new_class.__tracked__, OrderedDict): + tracked_attributes = new_class.__tracked__.copy() + + # Adds the class attributes and the declared class attributes that were retrieved above + tracked_attributes.update(tracked_class_attributes) + tracked_attributes.update(tracked_declared_class_attributes) + + # Adds the __tracked__ attribute to the new class and returns it + new_class.__tracked__ = tracked_attributes + tracked_new_class: MetaTracker = new_class + return tracked_new_class - Note - ---- - See PEP3115 +class Tracker(metaclass=MetaTracker): + """Tracks all public class attributes, i.e., all class attributes not enclosed int double underscores. This makes them available in a class + attribute ``__tracked__`` using the meta class ``MetaTracker``. """ + + __tracked__: OrderedDict[str, Any] + """An ``OrderedDict`` with all public class attributes, i.e., all class attributes not enclosed with double underscores.""" + @classmethod - # pylint: disable=bad-mcs-classmethod-argument,unused-argument - def __prepare__(metacls, name, bases): - """Prepare the class dict to be an `OrderedDict`. + def collect(cls, dtype: type | tuple[type, ...]) -> OrderedDict[str, Any]: + """Retrieves all tracked class attributes of a certain type. - Parameters - ---------- - name : str - Name of the class, or meta class instance. - bases : tuple of type - Bases of the class, or meta class instance. + Args: + dtype (type | tuple[type, ...]): The type or types of the class attributes to retrieve. + Returns: + OrderedDict[str, Any]: Returns an ``OrderedDict`` that contains the public class attributes, i.e., all class attributes not enclosed in + double underscores, of the given type or types. The keys are the attribute names and the values are the attribute values. """ - return PublicOrderedDict() - # pylint: disable=arguments-differ - def __new__(cls, classname, bases, class_dict): - """Instantiate a meta class, resulting in a class. + return OrderedDict( + (attribute_name, attribute_value) + for attribute_name, attribute_value in cls.__tracked__.items() + if isinstance(attribute_value, dtype) + ) - Parameters - ---------- - classname : str - Name of the class, or meta class instance. - bases : tuple of type - Bases of the class, or meta class instance. - class_dict : :obj:`InstanceTracker` - The class' to-be __dict__. In this case, with the addition of tracker attributes. + @classmethod + def get(cls, attribute_name: str) -> Any: + """Retrieves a tracked class attribute by name. - """ - result = super().__new__(cls, classname, bases, dict(class_dict)) - try: - tracked = result.__tracked__ - except AttributeError: - # if there is no attribute `__tracked__`, we do not inherit it - tracked = class_dict.public() - else: - # otherwise we inherit from another base, copy tracked dict and append our new one - tracked = result.__tracked__.copy() - tracked.update(class_dict.public()) - result.__tracked__ = tracked - return result + Args: + attribute_name (str): The name of the class attribute to retrieve. + Raises: + AttributeError: The class attribute does not exist. -class Tracker(metaclass=MetaTracker): - """Tracks all class attributes not enclosed with double underscores in order, and makes them available as - its __tracked__ attribute using MetaTracker. + Returns: + Any: Returns the value of the class attribute with the given name. If the class attribute does not exist `None` is returned. + """ - """ - @classmethod - def collect(cls, dtype): - """Return all tracked class attributes of a certain type. + if attribute_name not in cls.__tracked__: + raise AttributeError(f"Class attribute '{attribute_name}' does not exist.") + return cls.__tracked__.get(attribute_name) - Parameters - ---------- - dtype : type or tuple of type - Type(s) of the class attributes to collect. + def collect_attr(self, dtype: type | tuple[type, ...]) -> OrderedDict[str, Any]: + """Retrieves all instance attributes, corresponding to tracked class attributes of a certain type. - Returns - ------- - OrderedDict - An OrderedDict, with the attribute names as keys and their corresponding values. + Args: + dtype (type | tuple[type, ...]): The type or types of the instance attributes to retrieve. + Returns: + OrderedDict[str, Any]: Returns an ``OrderedDict`` that contains the instance attributes, corresponding to tracked class attributes, of the + given type or types. The keys are the attribute names and the values are the attribute values. """ - # pylint: disable=no-member - return OrderedDict((key, val) for key, val in cls.__tracked__.items() if isinstance(val, dtype)) - def collect_attr(self, dtype): - """Return all instance attributes, corresponding to tracked class attributes of a certain type. + return OrderedDict( + (attribute_name, getattr(self, attribute_name, None)) + for attribute_name, attribute_value in self.__tracked__.items() + if isinstance(attribute_value, dtype) + ) - Parameters - ---------- - dtype : type or tuple of type - Type(s) of the class attributes to collect. + def get_attr(self, attribute_name: str) -> Any: + """Retrieves an instance attribute, corresponding to a tracked class attribute, by name. - Returns - ------- - OrderedDict - An OrderedDict, with the attribute names as keys and the corresponding instance attribute values. + Args: + attribute_name (str): The name of the class attribute to retrieve. + Raises: + AttributeError: The instance attribute does not exist. + + Returns: + Any: Returns the value of the instance attribute with the given name. If the instance attribute does not exist `None` is returned. """ - # pylint: disable=no-member - return OrderedDict((key, getattr(self, key)) for key, val in self.__tracked__.items() if isinstance(val, dtype)) + + if attribute_name not in self.__tracked__: + raise AttributeError(f"Instance attribute '{attribute_name}' does not exist.") + return getattr(self, attribute_name) diff --git a/source/corelay/utils.py b/source/corelay/utils.py index 4c11834..326ce06 100644 --- a/source/corelay/utils.py +++ b/source/corelay/utils.py @@ -1,123 +1,235 @@ -"""CoRelAy utils contains conditional importing functionality. - +"""A module containing utility classes and functions for CoRelAy, such as an iterable with member type checking, an enhanced zip function, and +conditional import functions. """ +from collections.abc import Callable, Iterable, Iterator from importlib import import_module -from typing import Iterable as IterableBase +from types import ModuleType +from typing import Any, NoReturn, overload + + +def zip_equal(*args: Iterable[Any]) -> Iterator[tuple[Any, ...]]: + """Zips its positional arguments, but only if they are of equal length. + Args: + *args (Iterable[Any]): The iterables that are to be zipped. They must be of equal length. -class IterableMeta(type): - """Meta class to implement member instance checking for Iterables""" - def __instancecheck__(cls, instance): - """Is instance if iterable and all members are of provided types""" - return isinstance(instance, IterableBase) and all(isinstance(obj, cls.__membertype__) for obj in instance) + Raises: + TypeError: The positional arguments have different lengths. + Yields: + tuple[Any, ...]: Yields the zipped elements of the positional arguments. + """ -class Iterable(metaclass=IterableMeta): - """Iterables with strict member type checking""" - __membertype__ = object + iterators = [iter(iterable) for iterable in args] - def __class_getitem__(cls, params): - """Dynamically creates a subclass with the provided member types""" - if not isinstance(params, tuple): - params = (params,) - if not params: - raise TypeError("At least one member type must be specified!") - if not all(isinstance(obj, type) for obj in params): - raise TypeError("Member types must be types!") - # pylint: disable=no-member - return type(f'{cls.__name__}[{params}]', (cls,), {'__membertype__': params}) + has_any_iterator_stopped = False + while not has_any_iterator_stopped: + zipped_element = [] + has_any_iterator_more_elements = False + for iterator in iterators: + try: + value = next(iterator) + except StopIteration: + has_any_iterator_stopped = True + else: + has_any_iterator_more_elements = True + zipped_element.append(value) + if has_any_iterator_stopped and has_any_iterator_more_elements: + raise TypeError('The iterables have different lengths.') -def zip_equal(*args): - """Zip positional arguments only if they are of equal length. + if not has_any_iterator_stopped: + yield tuple(zipped_element) - Parameters - ---------- - *args : Iterable - Iterables of equal length to be zipped - Yields - ------ - tuple - Zipped elements +def dummy_from_module_import(module_name: str) -> Callable[..., Any]: + """Creates a stub function that raises an error when called. This is used to replace an actual import of a type or function from a module, like + `from module import type` or `from module import function`, of a package that has not been installed. It is useful for optional dependencies of a + package, because the retrieved function will raise an exception that tells the user how to install the missing dependencies for the functionality + to work. It does not matter if the user meant to import a function or a type, as types are also callable (i.e., if a type is called, then an + instance of the type is created and the constructor ``__init__`` is called) and invoking a function is syntactically indistinguishable from + instantiating an instance of a type. If the user meant to import a function, then the exception will be raised when the function is called. If the + user meant to import a type, then the exception will be raised when the user tries to instantiate an instance of the type. - Raises - ------ - TypeError - If positional arguments are no Iterables, or have different length. + Args: + module_name (str): The name of the module from which the type or function that is to be replaced was imported. + Returns: + Callable[..., Any]: Returns a function that raises an error when called, which instructs the user on how to install the missing dependencies. """ - iterator_list = [iter(obj) for obj in args] - stop = False - while not stop: - more = False - result = [] - for iterator in iterator_list: - try: - value = next(iterator) - except StopIteration: - stop = True - else: - more = True - result.append(value) - if stop and more: - raise TypeError("Unequal length!") - if not stop: - yield tuple(result) + def function(*_args: Any, **_kwargs: Any) -> NoReturn: + """A stub function that raises an error when called, which instructs the user on how to install the missing dependencies. -def dummy_from_module_import(name): - """Use to replace 'from lib import func'.""" - def func(*args, **kwargs): # pylint: disable=unused-argument + Args: + *_args (Any): The positional arguments that were passed to the function. + **_kwargs (Any): The keyword arguments that were passed to the function. + + Raises: + RuntimeError: Always raises a ``RuntimeError`` with a message indicating how to install the missing dependencies. + """ + + # Retrieves the name of this package, which is the first part of the fully-qualified name of this module; this should always be "corelay", + # unless the project will be renamed at some point in the future package_name = __name__.split('.', maxsplit=1)[0] - raise RuntimeError(f"Support for {name} was not installed! Install with: pip install {package_name}[{name}]") - return func + raise RuntimeError( + f'Support for "{module_name}" was not installed. The missing dependencies can be installed as follows: ' + f'pip install {package_name}[{module_name}].' + ) + + return function + + +def dummy_import_module(module_name: str) -> Any: + """Creates a stub class that raises an error when any of its attributes are accessed and returns an instance of it. This is used to replace an + actual import of a module, like `import module`, of a package that has not been installed. It is useful for optional dependencies of a package, + because the retrieved object will raise an exception that tells the user how to install the missing dependencies for the functionality to work. It + does not matter that the user meant to import a module and not a class, as accessing a module's members is syntactically indistinguishable from + accessing a class's attributes. So of the user imported a module, the exception will be raised when the user tries to access any of the module's + members. + + Args: + module_name (str): The name of the module that is to be replaced. + + Returns: + Any: Returns an instance of a class that raises an error when any of its attributes are accessed, which instructs the user on how to install + the missing dependencies. + """ -def dummy_import_module(name): - """Use to replace 'import lib`.""" class Class: - """Dummy substitute class.""" - def __getattr__(self, item): + """A stub class that raises an error when any of its attributes are access, which instructs the user on how to install the missing + dependencies. + """ + + def __getattr__(self, _item: str) -> NoReturn: + """Is invoked when an attribute of the class is accessed. It raises a ``RuntimeError`` with a message indicating how to install the + missing dependencies. + + Args: + _item (str): The name of the attribute that was accessed. + + Raises: + RuntimeError: Always raises a ``RuntimeError`` with a message indicating how to install the missing dependencies. + """ + + # Retrieves the name of this package, which is the first part of the fully-qualified name of this module; this should always be "corelay", + # unless the project will be renamed at some point in the future package_name = __name__.split('.', maxsplit=1)[0] + raise RuntimeError( - f"Support for {name} was not installed! Install with: pip install {package_name}[{name}]" + f'Support for "{module_name}" was not installed. The missing dependencies can be installed as follows: ' + f'pip install {package_name}[{module_name}].' ) + return Class() -def import_or_stub(name, subname=None): - """Use to conditionally import packages. +@overload +def import_or_stub(module_name: str) -> ModuleType: + """Tries to import a module. If the import fails, the requested module is replaced with a dummy that will raise an exception when used. This is + useful for optional dependencies of a package, because the retrieved module will raise an exception that tells the user how to install the missing + dependencies for the functionality to work. + + Args: + module_name (str): The name of the module that is to be imported. + + Returns: + ModuleType: Returns the imported module. If the import of the module fails, a dummy is returned that will raise an exception when used, + telling the user how to install the missing dependencies. + """ + + +@overload +def import_or_stub(module_name: str, type_and_function_names: str) -> Callable[..., Any]: + """Tries to import a type or a function from a module. If the import fails, the requested type or function is replaced with a dummy that will + raise an exception when used. This is useful for optional dependencies of a package, because the retrieved type or function will raise an + exception that tells the user how to install the missing dependencies for the functionality to work. + + Args: + module_name (str): The name of the module that is to be imported or from which the specified type or function is to be imported. + type_and_function_names (str): The name of the type or function that is to be imported from the module. - Parameters - ---------- - name: str - Module name. Ie. 'module.lib' - subname: tuple[str] or str or None - Functions or classes to be imported from 'name'. + Returns: + Callable[..., Any]: Returns the imported type or function. If the import of the module fails, a dummy is returned that will raise an + exception when used, telling the user how to install the missing dependencies. """ - subnames = (subname, ) if isinstance(subname, str) else subname # convert to tuple - if subname is not None: + + +@overload +def import_or_stub(module_name: str, type_and_function_names: tuple[str, ...]) -> list[ModuleType | Callable[..., Any]]: + """Tries to import types and/or functions from a module. If the import fails, the requested types and/or functions are replaced with dummies that + will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved types and/or functions will raise + an exception that tells the user how to install the missing dependencies for the functionality to work. + + Args: + module_name (str): The name of the module that is to be imported or from which the specified types and/or functions are to be imported. + type_and_function_names (tuple[str, ...]): The names of the types and/or functions that are to be imported from the module. + + Returns: + list[ModuleType | Callable[..., Any]]: Returns a list of the imported modules, types and/or functions. If the import of the module fails, + dummies are returned that will raise an exception when used, telling the user how to install the missing dependencies. + """ + + +def import_or_stub( + module_name: str, + type_and_function_names: str | tuple[str, ...] | None = None +) -> list[ModuleType | Callable[..., Any]] | ModuleType | Callable[..., Any]: + """Tries to import a module, or types and/or functions from a module. If the import fails, the requested module, or types and/or functions are + replaced with dummies that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved module, + or types and/or functions will raise an exception that tells the user how to install the missing dependencies for the functionality to work. + + Args: + module_name (str): The name of the module that is to be imported or from which the specified types and/or functions are to be imported. + type_and_function_names (str | tuple[str, ...] | None, optional): The names of the types and/or functions that are to be imported from the + module, e.g., `'function'` or `('function', 'type')`. If this argument is not provided, the entire module is imported. If the module, or + the types and/or functions cannot be imported, dummies will be returned that will raise an exception when used. Defaults to `None`. + + Raises: + ImportError: If a type or function cannot be imported from a module that is installed, an ``ImportError`` is raised. This is done, instead of + stubbing the imported type or function, because this always indicates a bug in the code and cannot be fixed by the user installing a + missing dependency. + + Returns: + list[ModuleType | Callable[..., Any]] | ModuleType | Callable[..., Any]: Returns the imported module, or types and/or functions that were + imported from the module. If the import of the module fails, dummies for the module, or types and/or functions are returned that will + raise an exception when used, telling the user how to install the missing dependencies. + """ + + # Creates a list of the modules, types, and functions that were imported + modules_types_and_functions: list[ModuleType | type | Callable[..., Any]] = [] + + # If a list of types and functions to import was provided, then they are imported (or replaced by dummies) and added to the list + if type_and_function_names is not None: + type_and_function_names = (type_and_function_names, ) if isinstance(type_and_function_names, str) else type_and_function_names + try: - tmp = import_module(name) + imported_module = import_module(module_name) except ImportError: - module = [dummy_from_module_import(name) for _ in subnames] + modules_types_and_functions = [dummy_from_module_import(module_name) for _ in type_and_function_names] else: - module = [] - for model_attribute in subnames: + for type_or_function_name in type_and_function_names: try: - attr = getattr(tmp, model_attribute) - except AttributeError as err: - message = f"cannot import name '{model_attribute}' from '{name}' ({tmp.__file__})" - raise ImportError(message) from err - else: - module.append(attr) - if len(module) == 1: - module = module[0] + type_or_function = getattr(imported_module, type_or_function_name) + except AttributeError as exception: + raise ImportError( + f'Cannot import name "{type_or_function_name}" from "{module_name}" ({imported_module.__file__}).' + ) from exception + + modules_types_and_functions.append(type_or_function) + + # If, however, no list of types and functions to import was provided, then the entire module is imported (or replaced by a stub) and added to the + # list else: try: - module = import_module(name) + modules_types_and_functions = [import_module(module_name)] except ImportError: - module = dummy_import_module(name) - return module + modules_types_and_functions = [dummy_import_module(module_name)] + + # If only a single module/type/function was imported, then it is returned directly, otherwise a list of the imported modules/types/functions is + # returned + if len(modules_types_and_functions) == 1: + return modules_types_and_functions[0] + return modules_types_and_functions diff --git a/source/pyproject.toml b/source/pyproject.toml index be946f8..5c4bc79 100644 --- a/source/pyproject.toml +++ b/source/pyproject.toml @@ -44,11 +44,10 @@ authors = [ # The project's dependencies, which are included in the requirements of the published package dependencies = [ - "click>=8.1.8,<9.0.0", "h5py>=3.13.0,<4.0.0", "matplotlib>=3.10.1,<4.0.0", "metrohash-python>=1.1.3.3,<2.0.0", - "numpy>=2.2.4,<3.0.0", + "numpy>=2.2.5,<3.0.0", "scikit-image>=0.25.2,<1.0.0", "scikit-learn>=1.6.1,<2.0.0", "scipy>=1.15.2,<2.0.0" @@ -75,30 +74,57 @@ Repository = "https://github.com/virelay/corelay.git" Issues = "https://github.com/virelay/corelay/issues" Changelog = "https://github.com/virelay/corelay/blob/main/CHANGELOG.md" -# Configuration specified to the UV Python package manager -[tool.uv] +# Groups of extra dependencies that are not included in the requirements of the published package, but users may install them optionally; installing +# them provides additional functionality, but is not required to use the package +[project.optional-dependencies] -# Dependencies that are only installed during the development of the project and are not included in the requirements of the published package -dev-dependencies = [ +# Dependencies required for using the Uniform Manifold Approximation and Projection (UMAP) dimensionality reduction algorithm +umap = [ + "umap-learn>=0.5.7,<1.0.0" +] - # Dependencies required for building the documentation - "sphinx>=8.2.3,<9.0.0", - "sphinx-copybutton>=0.5.2,<1.0.0", - "sphinx-rtd-theme>=3.0.2,<4.0.0", - "sphinxcontrib.bibtex>=2.6.3,<3.0.0", - "sphinxcontrib.datatemplates>=0.11.0,<1.0.0", +# Dependencies required for using the Hierarchical Density-Based Spatial Clustering of Applications with Noise (HDBSCAN) clustering algorithm +hdbscan = [ + "hdbscan>=0.8.40,<1.0.0" +] + +# Groups of development dependencies that are not included in the requirements of the published package; they can be installed separately or +# all-in-one with the "dev" group, which includes all development dependencies +[dependency-groups] + +# Dependencies required for development; this group includes all other groups +dev = [ + { include-group = "testing" }, + { include-group = "linting" }, + { include-group = "docs" } +] - # Dependencies required for testing +# Dependencies required for testing +testing = [ "tox>=4.25.0,<5.0.0", "tox-uv>=1.25.0,<2.0.0", - "coverage>=7.8.0,<8.0.0", - "pytest-cov>=6.1.1,<7.0.0", "pytest>=8.3.5,<9.0.0", - "setuptools>=78.1.0,<79.0.0", + "coverage>=7.8.0,<8.0.0", + "pytest-cov>=6.1.1,<7.0.0" +] + +# Dependencies required for linting +linting = [ + "pylint>=3.3.6,<4.0.0", + "pycodestyle>=2.13.0,<3.0.0", + "pydoclint>=0.6.6,<1.0.0", + "mypy>=1.15.0,<2.0.0", + "scipy-stubs>=1.15.2.2,<2.0.0", + "pytest-mypy>=1.0.1,<2.0.0" +] - # Dependencies required for linting - "flake8>=7.2.0,<8.0.0", - "pylint>=3.3.6,<4.0.0" +# Dependencies required for building the documentation +docs = [ + "sphinx>=8.2.3,<9.0.0", + "sphinx-copybutton>=0.5.2,<1.0.0", + "sphinx-rtd-theme>=3.0.2,<4.0.0", + "sphinxcontrib.bibtex>=2.6.3,<3.0.0", + "sphinxcontrib.datatemplates>=0.11.0,<1.0.0" ] # Configures which build system is used by UV to build the project diff --git a/source/tox.ini b/source/tox.ini new file mode 100644 index 0000000..265d48f --- /dev/null +++ b/source/tox.ini @@ -0,0 +1,120 @@ + +# Core tox configuration +[tox] + +# A list of environments that will be run by default when running tox without specifying any environment +envlist = + py311 + py312 + py313 + coverage + pylint + pycodestyle + pydoclint + mypy + docs + +# Base configuration for test environments that tox will fallback to for missing values; this avoids having to repeat the same configuration for the +# unit test environments py311, py312, and py313 over and over again +[testenv] +uv_python_preference = only-managed +dependency_groups = testing +setenv = + COVERAGE_FILE = ../tests/unit_tests/coverage/.coverage.{envname} +commands = + python -m pytest \ + --config-file "../tests/unit_tests/.pytest.ini" \ + --cov corelay \ + --cov-config "../tests/unit_tests/.coveragerc" \ + --cov-append \ + {posargs:../tests/unit_tests} + +# A test environment that combines the coverage data from all runs of the unit tests with the different Python versions and generates a single report +[testenv:coverage] +setenv = + COVERAGE_FILE = ../tests/unit_tests/coverage/.coverage +depends = + py311 + py312 + py313 +commands = + coverage combine \ + --rcfile "../tests/unit_tests/.coveragerc" + coverage report \ + --rcfile "../tests/unit_tests/.coveragerc" \ + --show-missing + coverage html \ + --rcfile "../tests/unit_tests/.coveragerc" \ + --directory "../tests/unit_tests/coverage/report" + +# A test environment that will build the documentation using Sphinx +[testenv:docs] +base_python = py313 +dependency_groups = docs +commands = + sphinx-build \ + --color \ + --fail-on-warning \ + --keep-going \ + --doctree-dir "../docs/doctree" \ + --builder html \ + "../docs/source" \ + "../docs/build" \ + {posargs} + +# A test environment that will run the PyLint linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples; PyLint +# not only requires the linting dependency group, but also the testing and the docs dependency groups to be installed, because PyLint will try to +# import the packages in the modules its it linting, and the unit tests use the PyTest package and the Sphinx configuration uses the PybTeX and the +# Sphinx packages +[testenv:pylint] +base_python = py313 +dependency_groups = + testing + linting + docs +commands = + pylint \ + --rcfile="../tests/linters/.pylintrc" \ + --output-format="parseable" \ + corelay \ + "../tests/unit_tests" \ + "../docs/source/conf.py" \ + "../example" + +# A test environment that will run the PyCodeStyle linter on the REST API backend, the unit tests, the setup script, and the Sphinx configuration file +[testenv:pycodestyle] +base_python = py313 +dependency_groups = linting +commands = + pycodestyle \ + --config="../tests/linters/.pycodestyle" \ + corelay \ + "../tests/unit_tests" \ + "../docs/source/conf.py" \ + "../example" + +# A test environment that will run the PyDocLint docstring linter on the REST API backend, the unit tests, the setup script, and the Sphinx +# configuration file +[testenv:pydoclint] +base_python = py313 +dependency_groups = linting +commands = + pydoclint \ + --config="../tests/linters/.pydoclint.toml" \ + corelay \ + "../tests/unit_tests" \ + "../docs/source/conf.py" \ + "../example" + +# A test environment that will run the MyPy static type checker on the REST API backend, the unit tests, the setup script, and the Sphinx +# configuration file +[testenv:mypy] +base_python = py313 +dependency_groups = linting +commands = + mypy \ + --config-file="../tests/linters/.mypy.ini" \ + corelay \ + "../tests/unit_tests" \ + "../docs/source/conf.py" \ + "../example" diff --git a/source/uv.lock b/source/uv.lock index 2e9f27d..3f1b980 100644 --- a/source/uv.lock +++ b/source/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 1 +revision = 2 requires-python = ">=3.11.12" resolution-markers = [ "python_full_version >= '3.12'", @@ -10,102 +10,102 @@ resolution-markers = [ name = "alabaster" version = "0.7.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +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 }, + { 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 = "astroid" version = "3.3.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731 } +sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731, upload_time = "2025-03-09T11:54:36.388Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339 }, + { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339, upload_time = "2025-03-09T11:54:34.489Z" }, ] [[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 } +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 }, + { 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 = "cachetools" version = "5.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload_time = "2025-02-20T21:01:19.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload_time = "2025-02-20T21:01:16.647Z" }, ] [[package]] name = "certifi" version = "2025.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload_time = "2025-01-31T02:16:47.166Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload_time = "2025-01-31T02:16:45.015Z" }, ] [[package]] name = "chardet" version = "5.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload_time = "2023-08-01T19:23:02.662Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload_time = "2023-08-01T19:23:00.661Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload_time = "2024-12-24T18:12:35.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload_time = "2024-12-24T18:10:12.838Z" }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload_time = "2024-12-24T18:10:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload_time = "2024-12-24T18:10:15.512Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload_time = "2024-12-24T18:10:18.369Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload_time = "2024-12-24T18:10:19.743Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload_time = "2024-12-24T18:10:21.139Z" }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload_time = "2024-12-24T18:10:22.382Z" }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload_time = "2024-12-24T18:10:24.802Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload_time = "2024-12-24T18:10:26.124Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload_time = "2024-12-24T18:10:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload_time = "2024-12-24T18:10:32.679Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload_time = "2024-12-24T18:10:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload_time = "2024-12-24T18:10:37.574Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload_time = "2024-12-24T18:10:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload_time = "2024-12-24T18:10:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload_time = "2024-12-24T18:10:45.492Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload_time = "2024-12-24T18:10:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload_time = "2024-12-24T18:10:50.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload_time = "2024-12-24T18:10:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload_time = "2024-12-24T18:10:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload_time = "2024-12-24T18:10:55.048Z" }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload_time = "2024-12-24T18:10:57.647Z" }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload_time = "2024-12-24T18:10:59.43Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload_time = "2024-12-24T18:11:00.676Z" }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload_time = "2024-12-24T18:11:01.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload_time = "2024-12-24T18:11:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload_time = "2024-12-24T18:11:05.834Z" }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload_time = "2024-12-24T18:11:07.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload_time = "2024-12-24T18:11:08.374Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload_time = "2024-12-24T18:11:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload_time = "2024-12-24T18:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload_time = "2024-12-24T18:11:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload_time = "2024-12-24T18:11:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload_time = "2024-12-24T18:11:17.672Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload_time = "2024-12-24T18:11:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload_time = "2024-12-24T18:11:21.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload_time = "2024-12-24T18:11:22.774Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload_time = "2024-12-24T18:11:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload_time = "2024-12-24T18:11:26.535Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload_time = "2024-12-24T18:12:32.852Z" }, ] [[package]] @@ -115,18 +115,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload_time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload_time = "2024-12-21T18:38:41.666Z" }, ] [[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 } +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 }, + { 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]] @@ -136,58 +136,57 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } -wheels = [ - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196 }, - { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489 }, - { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535 }, - { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550 }, - { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791 }, +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/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/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 = "corelay" source = { editable = "." } dependencies = [ - { name = "click" }, { name = "h5py" }, { name = "matplotlib" }, { name = "metrohash-python" }, @@ -197,14 +196,25 @@ dependencies = [ { name = "scipy" }, ] +[package.optional-dependencies] +hdbscan = [ + { name = "hdbscan" }, +] +umap = [ + { name = "umap-learn" }, +] + [package.dev-dependencies] dev = [ { name = "coverage" }, - { name = "flake8" }, + { name = "mypy" }, + { name = "pycodestyle" }, + { name = "pydoclint" }, { name = "pylint" }, { name = "pytest" }, { name = "pytest-cov" }, - { name = "setuptools" }, + { name = "pytest-mypy" }, + { name = "scipy-stubs" }, { name = "sphinx" }, { name = "sphinx-copybutton" }, { name = "sphinx-rtd-theme" }, @@ -213,27 +223,54 @@ dev = [ { name = "tox" }, { name = "tox-uv" }, ] +docs = [ + { name = "sphinx" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-bibtex" }, + { name = "sphinxcontrib-datatemplates" }, +] +linting = [ + { name = "mypy" }, + { name = "pycodestyle" }, + { name = "pydoclint" }, + { name = "pylint" }, + { name = "pytest-mypy" }, + { name = "scipy-stubs" }, +] +testing = [ + { name = "coverage" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "tox" }, + { name = "tox-uv" }, +] [package.metadata] requires-dist = [ - { name = "click", specifier = ">=8.1.8,<9.0.0" }, { name = "h5py", specifier = ">=3.13.0,<4.0.0" }, + { name = "hdbscan", marker = "extra == 'hdbscan'", specifier = ">=0.8.40,<1.0.0" }, { name = "matplotlib", specifier = ">=3.10.1,<4.0.0" }, { name = "metrohash-python", specifier = ">=1.1.3.3,<2.0.0" }, - { name = "numpy", specifier = ">=2.2.4,<3.0.0" }, + { name = "numpy", specifier = ">=2.2.5,<3.0.0" }, { name = "scikit-image", specifier = ">=0.25.2,<1.0.0" }, { name = "scikit-learn", specifier = ">=1.6.1,<2.0.0" }, { name = "scipy", specifier = ">=1.15.2,<2.0.0" }, + { name = "umap-learn", marker = "extra == 'umap'", specifier = ">=0.5.7,<1.0.0" }, ] +provides-extras = ["hdbscan", "umap"] [package.metadata.requires-dev] dev = [ { name = "coverage", specifier = ">=7.8.0,<8.0.0" }, - { name = "flake8", specifier = ">=7.2.0,<8.0.0" }, + { name = "mypy", specifier = ">=1.15.0,<2.0.0" }, + { name = "pycodestyle", specifier = ">=2.13.0,<3.0.0" }, + { name = "pydoclint", specifier = ">=0.6.6,<1.0.0" }, { name = "pylint", specifier = ">=3.3.6,<4.0.0" }, { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, { name = "pytest-cov", specifier = ">=6.1.1,<7.0.0" }, - { name = "setuptools", specifier = ">=78.1.0,<79.0.0" }, + { name = "pytest-mypy", specifier = ">=1.0.1,<2.0.0" }, + { name = "scipy-stubs", specifier = ">=1.15.2.2,<2.0.0" }, { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, { name = "sphinx-copybutton", specifier = ">=0.5.2,<1.0.0" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2,<4.0.0" }, @@ -242,156 +279,173 @@ dev = [ { name = "tox", specifier = ">=4.25.0,<5.0.0" }, { name = "tox-uv", specifier = ">=1.25.0,<2.0.0" }, ] +docs = [ + { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, + { name = "sphinx-copybutton", specifier = ">=0.5.2,<1.0.0" }, + { name = "sphinx-rtd-theme", specifier = ">=3.0.2,<4.0.0" }, + { name = "sphinxcontrib-bibtex", specifier = ">=2.6.3,<3.0.0" }, + { name = "sphinxcontrib-datatemplates", specifier = ">=0.11.0,<1.0.0" }, +] +linting = [ + { name = "mypy", specifier = ">=1.15.0,<2.0.0" }, + { name = "pycodestyle", specifier = ">=2.13.0,<3.0.0" }, + { name = "pydoclint", specifier = ">=0.6.6,<1.0.0" }, + { name = "pylint", specifier = ">=3.3.6,<4.0.0" }, + { name = "pytest-mypy", specifier = ">=1.0.1,<2.0.0" }, + { name = "scipy-stubs", specifier = ">=1.15.2.2,<2.0.0" }, +] +testing = [ + { name = "coverage", specifier = ">=7.8.0,<8.0.0" }, + { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, + { name = "pytest-cov", specifier = ">=6.1.1,<7.0.0" }, + { name = "tox", specifier = ">=4.25.0,<5.0.0" }, + { name = "tox-uv", specifier = ">=1.25.0,<2.0.0" }, +] [[package]] name = "coverage" version = "7.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493 }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921 }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556 }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245 }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032 }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679 }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852 }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389 }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997 }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911 }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684 }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935 }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994 }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885 }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142 }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906 }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124 }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317 }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170 }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969 }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443 }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 }, +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload_time = "2025-03-30T20:36:45.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload_time = "2025-03-30T20:35:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload_time = "2025-03-30T20:35:14.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload_time = "2025-03-30T20:35:15.616Z" }, + { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload_time = "2025-03-30T20:35:18.648Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload_time = "2025-03-30T20:35:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload_time = "2025-03-30T20:35:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload_time = "2025-03-30T20:35:23.525Z" }, + { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload_time = "2025-03-30T20:35:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload_time = "2025-03-30T20:35:26.914Z" }, + { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload_time = "2025-03-30T20:35:28.498Z" }, + { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload_time = "2025-03-30T20:35:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload_time = "2025-03-30T20:35:31.912Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload_time = "2025-03-30T20:35:33.455Z" }, + { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload_time = "2025-03-30T20:35:35.354Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload_time = "2025-03-30T20:35:37.121Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload_time = "2025-03-30T20:35:39.07Z" }, + { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload_time = "2025-03-30T20:35:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload_time = "2025-03-30T20:35:42.204Z" }, + { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload_time = "2025-03-30T20:35:44.216Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload_time = "2025-03-30T20:35:45.797Z" }, + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload_time = "2025-03-30T20:35:47.417Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload_time = "2025-03-30T20:35:49.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload_time = "2025-03-30T20:35:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload_time = "2025-03-30T20:35:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload_time = "2025-03-30T20:35:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload_time = "2025-03-30T20:35:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload_time = "2025-03-30T20:35:57.801Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload_time = "2025-03-30T20:35:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload_time = "2025-03-30T20:36:01.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload_time = "2025-03-30T20:36:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload_time = "2025-03-30T20:36:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload_time = "2025-03-30T20:36:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload_time = "2025-03-30T20:36:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload_time = "2025-03-30T20:36:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload_time = "2025-03-30T20:36:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload_time = "2025-03-30T20:36:13.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload_time = "2025-03-30T20:36:16.074Z" }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload_time = "2025-03-30T20:36:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload_time = "2025-03-30T20:36:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload_time = "2025-03-30T20:36:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload_time = "2025-03-30T20:36:41.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload_time = "2025-03-30T20:36:43.61Z" }, ] [[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 } +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 }, + { 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 } +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 }, + { 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 = "dill" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976 } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload_time = "2025-04-16T00:41:48.867Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668 }, + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload_time = "2025-04-16T00:41:47.671Z" }, ] [[package]] name = "distlib" version = "0.3.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload_time = "2024-10-09T18:35:47.551Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload_time = "2024-10-09T18:35:44.272Z" }, ] [[package]] -name = "docutils" -version = "0.21.2" +name = "docstring-parser-fork" +version = "0.0.12" 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 } +sdist = { url = "https://files.pythonhosted.org/packages/c6/72/61f7243ad62e14d527f93304cd4f333e681295aa3ef9bcc4afc36c07001a/docstring_parser_fork-0.0.12.tar.gz", hash = "sha256:b44c5e0be64ae80f395385f01497d381bd094a57221fd9ff020987d06857b2a0", size = 31608, upload_time = "2025-01-13T07:57:43.351Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, + { url = "https://files.pythonhosted.org/packages/50/0d/eed05d4b1065f11f8bef2f57440759d4dacc73c22780764948bfa4aaa304/docstring_parser_fork-0.0.12-py3-none-any.whl", hash = "sha256:55d7cbbc8b367655efd64372b9a0b33a49bae930a8ddd5cdc4c6112312e28a87", size = 42185, upload_time = "2025-01-13T07:57:39.993Z" }, ] [[package]] -name = "filelock" -version = "3.18.0" +name = "docutils" +version = "0.21.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +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/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, + { 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 = "flake8" -version = "7.2.0" +name = "filelock" +version = "3.18.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload_time = "2025-03-14T07:11:40.47Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786 }, + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload_time = "2025-03-14T07:11:39.145Z" }, ] [[package]] name = "fonttools" version = "4.57.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392 }, - { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609 }, - { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292 }, - { url = "https://files.pythonhosted.org/packages/41/b8/d5933559303a4ab18c799105f4c91ee0318cc95db4a2a09e300116625e7a/fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746", size = 4902503 }, - { url = "https://files.pythonhosted.org/packages/32/13/acb36bfaa316f481153ce78de1fa3926a8bad42162caa3b049e1afe2408b/fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344", size = 5077351 }, - { url = "https://files.pythonhosted.org/packages/b5/23/6d383a2ca83b7516d73975d8cca9d81a01acdcaa5e4db8579e4f3de78518/fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f", size = 5275067 }, - { url = "https://files.pythonhosted.org/packages/bc/ca/31b8919c6da0198d5d522f1d26c980201378c087bdd733a359a1e7485769/fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36", size = 2158263 }, - { url = "https://files.pythonhosted.org/packages/13/4c/de2612ea2216eb45cfc8eb91a8501615dd87716feaf5f8fb65cbca576289/fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d", size = 2204968 }, - { url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824 }, - { url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072 }, - { url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020 }, - { url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096 }, - { url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356 }, - { url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546 }, - { url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776 }, - { url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956 }, - { url = "https://files.pythonhosted.org/packages/e9/2f/11439f3af51e4bb75ac9598c29f8601aa501902dcedf034bdc41f47dd799/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef", size = 2739175 }, - { url = "https://files.pythonhosted.org/packages/25/52/677b55a4c0972dc3820c8dba20a29c358197a78229daa2ea219fdb19e5d5/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c", size = 2276583 }, - { url = "https://files.pythonhosted.org/packages/64/79/184555f8fa77b827b9460a4acdbbc0b5952bb6915332b84c615c3a236826/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72", size = 4766437 }, - { url = "https://files.pythonhosted.org/packages/f8/ad/c25116352f456c0d1287545a7aa24e98987b6d99c5b0456c4bd14321f20f/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817", size = 4838431 }, - { url = "https://files.pythonhosted.org/packages/53/ae/398b2a833897297797a44f519c9af911c2136eb7aa27d3f1352c6d1129fa/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9", size = 4951011 }, - { url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679 }, - { url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833 }, - { url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799 }, - { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605 }, +sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448, upload_time = "2025-04-03T11:07:13.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392, upload_time = "2025-04-03T11:05:45.715Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609, upload_time = "2025-04-03T11:05:47.977Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292, upload_time = "2025-04-03T11:05:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/b8/d5933559303a4ab18c799105f4c91ee0318cc95db4a2a09e300116625e7a/fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746", size = 4902503, upload_time = "2025-04-03T11:05:52.17Z" }, + { url = "https://files.pythonhosted.org/packages/32/13/acb36bfaa316f481153ce78de1fa3926a8bad42162caa3b049e1afe2408b/fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344", size = 5077351, upload_time = "2025-04-03T11:05:54.162Z" }, + { url = "https://files.pythonhosted.org/packages/b5/23/6d383a2ca83b7516d73975d8cca9d81a01acdcaa5e4db8579e4f3de78518/fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f", size = 5275067, upload_time = "2025-04-03T11:05:57.375Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ca/31b8919c6da0198d5d522f1d26c980201378c087bdd733a359a1e7485769/fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36", size = 2158263, upload_time = "2025-04-03T11:05:59.567Z" }, + { url = "https://files.pythonhosted.org/packages/13/4c/de2612ea2216eb45cfc8eb91a8501615dd87716feaf5f8fb65cbca576289/fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d", size = 2204968, upload_time = "2025-04-03T11:06:02.16Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824, upload_time = "2025-04-03T11:06:03.782Z" }, + { url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072, upload_time = "2025-04-03T11:06:05.533Z" }, + { url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020, upload_time = "2025-04-03T11:06:07.249Z" }, + { url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096, upload_time = "2025-04-03T11:06:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356, upload_time = "2025-04-03T11:06:11.294Z" }, + { url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546, upload_time = "2025-04-03T11:06:13.6Z" }, + { url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776, upload_time = "2025-04-03T11:06:15.643Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956, upload_time = "2025-04-03T11:06:17.534Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2f/11439f3af51e4bb75ac9598c29f8601aa501902dcedf034bdc41f47dd799/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef", size = 2739175, upload_time = "2025-04-03T11:06:19.583Z" }, + { url = "https://files.pythonhosted.org/packages/25/52/677b55a4c0972dc3820c8dba20a29c358197a78229daa2ea219fdb19e5d5/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c", size = 2276583, upload_time = "2025-04-03T11:06:21.753Z" }, + { url = "https://files.pythonhosted.org/packages/64/79/184555f8fa77b827b9460a4acdbbc0b5952bb6915332b84c615c3a236826/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72", size = 4766437, upload_time = "2025-04-03T11:06:23.521Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/c25116352f456c0d1287545a7aa24e98987b6d99c5b0456c4bd14321f20f/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817", size = 4838431, upload_time = "2025-04-03T11:06:25.423Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/398b2a833897297797a44f519c9af911c2136eb7aa27d3f1352c6d1129fa/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9", size = 4951011, upload_time = "2025-04-03T11:06:27.41Z" }, + { url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679, upload_time = "2025-04-03T11:06:29.804Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833, upload_time = "2025-04-03T11:06:31.737Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799, upload_time = "2025-04-03T11:06:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605, upload_time = "2025-04-03T11:07:11.341Z" }, ] [[package]] @@ -401,32 +455,51 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876 } +sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876, upload_time = "2025-02-18T16:04:01.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922, upload_time = "2025-02-18T16:02:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619, upload_time = "2025-02-18T16:02:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366, upload_time = "2025-02-18T16:02:44.544Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/c99f662d4832c8835453cf3476f95daa28372023bda4aa1fca9e97c24f09/h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782ff0ac39f455f21fd1c8ebc007328f65f43d56718a89327eec76677ebf238a", size = 4509058, upload_time = "2025-02-18T16:02:49.035Z" }, + { url = "https://files.pythonhosted.org/packages/56/89/e3ff23e07131ff73a72a349be9639e4de84e163af89c1c218b939459a98a/h5py-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:22ffe2a25770a2d67213a1b94f58006c14dce06933a42d2aaa0318c5868d1508", size = 2966428, upload_time = "2025-02-18T16:02:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/d8/20/438f6366ba4ded80eadb38f8927f5e2cd6d2e087179552f20ae3dbcd5d5b/h5py-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:477c58307b6b9a2509c59c57811afb9f598aedede24a67da808262dfa0ee37b4", size = 3384442, upload_time = "2025-02-18T16:02:56.545Z" }, + { url = "https://files.pythonhosted.org/packages/10/13/cc1cb7231399617d9951233eb12fddd396ff5d4f7f057ee5d2b1ca0ee7e7/h5py-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57c4c74f627c616f02b7aec608a8c706fe08cb5b0ba7c08555a4eb1dde20805a", size = 2917567, upload_time = "2025-02-18T16:03:00.079Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d9/aed99e1c858dc698489f916eeb7c07513bc864885d28ab3689d572ba0ea0/h5py-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:357e6dc20b101a805ccfd0024731fbaf6e8718c18c09baf3b5e4e9d198d13fca", size = 4669544, upload_time = "2025-02-18T16:03:05.675Z" }, + { url = "https://files.pythonhosted.org/packages/a7/da/3c137006ff5f0433f0fb076b1ebe4a7bf7b5ee1e8811b5486af98b500dd5/h5py-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6f13f9b5ce549448c01e4dfe08ea8d1772e6078799af2c1c8d09e941230a90d", size = 4932139, upload_time = "2025-02-18T16:03:10.129Z" }, + { url = "https://files.pythonhosted.org/packages/25/61/d897952629cae131c19d4c41b2521e7dd6382f2d7177c87615c2e6dced1a/h5py-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:21daf38171753899b5905f3d82c99b0b1ec2cbbe282a037cad431feb620e62ec", size = 2954179, upload_time = "2025-02-18T16:03:13.716Z" }, + { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040, upload_time = "2025-02-18T16:03:20.579Z" }, + { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766, upload_time = "2025-02-18T16:03:26.831Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255, upload_time = "2025-02-18T16:03:31.903Z" }, + { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580, upload_time = "2025-02-18T16:03:36.429Z" }, + { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890, upload_time = "2025-02-18T16:03:41.037Z" }, +] + +[[package]] +name = "hdbscan" +version = "0.8.40" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/84/6b010387b795f774e1ec695df3c8660c15abd041783647d5e7e4076bfc6b/hdbscan-0.8.40.tar.gz", hash = "sha256:c9e383ff17beee0591075ff65d524bda5b5a35dfb01d218245a7ba30c8d48a17", size = 6904096, upload_time = "2024-11-18T16:14:05.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922 }, - { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619 }, - { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366 }, - { url = "https://files.pythonhosted.org/packages/03/71/c99f662d4832c8835453cf3476f95daa28372023bda4aa1fca9e97c24f09/h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782ff0ac39f455f21fd1c8ebc007328f65f43d56718a89327eec76677ebf238a", size = 4509058 }, - { url = "https://files.pythonhosted.org/packages/56/89/e3ff23e07131ff73a72a349be9639e4de84e163af89c1c218b939459a98a/h5py-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:22ffe2a25770a2d67213a1b94f58006c14dce06933a42d2aaa0318c5868d1508", size = 2966428 }, - { url = "https://files.pythonhosted.org/packages/d8/20/438f6366ba4ded80eadb38f8927f5e2cd6d2e087179552f20ae3dbcd5d5b/h5py-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:477c58307b6b9a2509c59c57811afb9f598aedede24a67da808262dfa0ee37b4", size = 3384442 }, - { url = "https://files.pythonhosted.org/packages/10/13/cc1cb7231399617d9951233eb12fddd396ff5d4f7f057ee5d2b1ca0ee7e7/h5py-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57c4c74f627c616f02b7aec608a8c706fe08cb5b0ba7c08555a4eb1dde20805a", size = 2917567 }, - { url = "https://files.pythonhosted.org/packages/9e/d9/aed99e1c858dc698489f916eeb7c07513bc864885d28ab3689d572ba0ea0/h5py-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:357e6dc20b101a805ccfd0024731fbaf6e8718c18c09baf3b5e4e9d198d13fca", size = 4669544 }, - { url = "https://files.pythonhosted.org/packages/a7/da/3c137006ff5f0433f0fb076b1ebe4a7bf7b5ee1e8811b5486af98b500dd5/h5py-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6f13f9b5ce549448c01e4dfe08ea8d1772e6078799af2c1c8d09e941230a90d", size = 4932139 }, - { url = "https://files.pythonhosted.org/packages/25/61/d897952629cae131c19d4c41b2521e7dd6382f2d7177c87615c2e6dced1a/h5py-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:21daf38171753899b5905f3d82c99b0b1ec2cbbe282a037cad431feb620e62ec", size = 2954179 }, - { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040 }, - { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766 }, - { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255 }, - { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580 }, - { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890 }, + { url = "https://files.pythonhosted.org/packages/26/6b/88b8c8023c0c0b27589ad83c82084a1b751917a3e09bdf7fcacf7e6bd523/hdbscan-0.8.40-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e958f0d7a33cd2b5e8e927b47f7360bf8a3e7d72355dd65a701e8aabe407b27", size = 1491349, upload_time = "2024-11-18T16:16:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ef/32c8a0b3dc6e6c4e433b85b30c3723d8eb48d115c0185b82ab89e1a0ef89/hdbscan-0.8.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0d6197ee045b173e1f16e6884386f335a56091e373a839dd24f7331a8fa9ed", size = 4576215, upload_time = "2024-11-18T16:14:11.241Z" }, + { url = "https://files.pythonhosted.org/packages/64/b1/96c347c7740efa1ac803be64155159284f92fafcff88c1077344e64eead5/hdbscan-0.8.40-cp311-cp311-win_amd64.whl", hash = "sha256:127cbe8c858dc77adfde33a3e1ce4f3bea810f78b01d2bd47b1147d4b5a50472", size = 732173, upload_time = "2024-11-18T16:18:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/33/ff/4739886abb990dc6feb7b02eafb38a7eaf090fffef6336e70a03d693f433/hdbscan-0.8.40-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:353eaa22e42bee69df095744dbb8b29360e516bd9dcb84580dceeeb755f004cc", size = 1497291, upload_time = "2024-11-18T16:16:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cb/6b4254f8a33e075118512e55acf3485c155ea52c6c35d69a985bdc59297c/hdbscan-0.8.40-cp312-cp312-win_amd64.whl", hash = "sha256:1b55a935ed7b329adac52072e1c4028979dfc54312ca08de2deece9c97d6ebb1", size = 726198, upload_time = "2024-11-18T16:18:09.99Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" }, ] [[package]] @@ -437,36 +510,36 @@ dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963 } +sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload_time = "2025-01-20T02:42:37.089Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796 }, + { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload_time = "2025-01-20T02:42:34.931Z" }, ] [[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 } +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 }, + { 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 = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +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 }, + { 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 = "isort" version = "5.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303, upload_time = "2023-12-13T20:37:26.124Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload_time = "2023-12-13T20:37:23.244Z" }, ] [[package]] @@ -476,93 +549,93 @@ 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 } +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 }, + { 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 = "joblib" version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } +sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621, upload_time = "2024-05-02T12:15:05.765Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, + { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817, upload_time = "2024-05-02T12:15:00.765Z" }, ] [[package]] name = "kiwisolver" version = "1.4.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, - { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, - { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, - { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994 }, - { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804 }, - { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690 }, - { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839 }, - { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109 }, - { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269 }, - { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468 }, - { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394 }, - { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901 }, - { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306 }, - { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966 }, - { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311 }, - { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 }, - { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 }, - { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 }, - { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 }, - { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 }, - { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 }, - { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 }, - { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 }, - { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 }, - { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 }, - { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 }, - { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 }, - { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 }, - { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 }, - { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 }, - { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156 }, - { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555 }, - { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071 }, - { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053 }, - { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278 }, - { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139 }, - { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517 }, - { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952 }, - { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132 }, - { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997 }, - { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060 }, - { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471 }, - { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793 }, - { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855 }, - { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430 }, - { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294 }, - { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736 }, - { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194 }, - { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942 }, - { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341 }, - { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455 }, - { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138 }, - { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857 }, - { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129 }, - { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538 }, - { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, - { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, - { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload_time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload_time = "2024-12-24T18:28:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload_time = "2024-12-24T18:28:54.256Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload_time = "2024-12-24T18:28:55.184Z" }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload_time = "2024-12-24T18:28:57.493Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload_time = "2024-12-24T18:29:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload_time = "2024-12-24T18:29:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload_time = "2024-12-24T18:29:02.685Z" }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload_time = "2024-12-24T18:29:04.113Z" }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload_time = "2024-12-24T18:29:05.488Z" }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload_time = "2024-12-24T18:29:06.79Z" }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload_time = "2024-12-24T18:29:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload_time = "2024-12-24T18:29:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload_time = "2024-12-24T18:29:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload_time = "2024-12-24T18:29:14.089Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload_time = "2024-12-24T18:29:15.892Z" }, + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload_time = "2024-12-24T18:29:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload_time = "2024-12-24T18:29:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload_time = "2024-12-24T18:29:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload_time = "2024-12-24T18:29:22.843Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload_time = "2024-12-24T18:29:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload_time = "2024-12-24T18:29:25.776Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload_time = "2024-12-24T18:29:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload_time = "2024-12-24T18:29:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload_time = "2024-12-24T18:29:30.368Z" }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload_time = "2024-12-24T18:29:33.151Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload_time = "2024-12-24T18:29:34.584Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload_time = "2024-12-24T18:29:36.138Z" }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload_time = "2024-12-24T18:29:39.991Z" }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload_time = "2024-12-24T18:29:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload_time = "2024-12-24T18:29:44.38Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload_time = "2024-12-24T18:29:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload_time = "2024-12-24T18:29:46.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload_time = "2024-12-24T18:29:47.333Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload_time = "2024-12-24T18:29:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload_time = "2024-12-24T18:29:51.164Z" }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload_time = "2024-12-24T18:29:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload_time = "2024-12-24T18:29:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload_time = "2024-12-24T18:29:56.523Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload_time = "2024-12-24T18:29:57.989Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload_time = "2024-12-24T18:29:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload_time = "2024-12-24T18:30:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload_time = "2024-12-24T18:30:04.574Z" }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload_time = "2024-12-24T18:30:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload_time = "2024-12-24T18:30:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload_time = "2024-12-24T18:30:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload_time = "2024-12-24T18:30:09.508Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload_time = "2024-12-24T18:30:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload_time = "2024-12-24T18:30:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload_time = "2024-12-24T18:30:18.927Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload_time = "2024-12-24T18:30:22.102Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload_time = "2024-12-24T18:30:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload_time = "2024-12-24T18:30:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload_time = "2024-12-24T18:30:28.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload_time = "2024-12-24T18:30:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload_time = "2024-12-24T18:30:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload_time = "2024-12-24T18:30:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload_time = "2024-12-24T18:30:37.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload_time = "2024-12-24T18:30:40.019Z" }, ] [[package]] name = "latexcodec" version = "3.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/e7/ed339caf3662976949e4fdbfdf4a6db818b8d2aa1cf2b5f73af89e936bba/latexcodec-3.0.0.tar.gz", hash = "sha256:917dc5fe242762cc19d963e6548b42d63a118028cdd3361d62397e3b638b6bc5", size = 31023 } +sdist = { url = "https://files.pythonhosted.org/packages/98/e7/ed339caf3662976949e4fdbfdf4a6db818b8d2aa1cf2b5f73af89e936bba/latexcodec-3.0.0.tar.gz", hash = "sha256:917dc5fe242762cc19d963e6548b42d63a118028cdd3361d62397e3b638b6bc5", size = 31023, upload_time = "2024-03-06T14:51:39.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/bf/ea8887e9f31a8f93ca306699d11909c6140151393a4216f0d9f85a004077/latexcodec-3.0.0-py3-none-any.whl", hash = "sha256:6f3477ad5e61a0a99bd31a6a370c34e88733a6bad9c921a3ffcfacada12f41a7", size = 18150 }, + { url = "https://files.pythonhosted.org/packages/b0/bf/ea8887e9f31a8f93ca306699d11909c6140151393a4216f0d9f85a004077/latexcodec-3.0.0-py3-none-any.whl", hash = "sha256:6f3477ad5e61a0a99bd31a6a370c34e88733a6bad9c921a3ffcfacada12f41a7", size = 18150, upload_time = "2024-03-06T14:51:37.872Z" }, ] [[package]] @@ -572,57 +645,80 @@ 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 } +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 }, + { 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 = "llvmlite" +version = "0.44.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880, upload_time = "2025-01-20T11:14:41.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/e2/86b245397052386595ad726f9742e5223d7aea999b18c518a50e96c3aca4/llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", size = 28132305, upload_time = "2025-01-20T11:12:53.936Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ec/506902dc6870249fbe2466d9cf66d531265d0f3a1157213c8f986250c033/llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", size = 26201090, upload_time = "2025-01-20T11:12:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/99/fe/d030f1849ebb1f394bb3f7adad5e729b634fb100515594aca25c354ffc62/llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", size = 42361858, upload_time = "2025-01-20T11:13:07.623Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/ce6174664b9077fc673d172e4c888cb0b128e707e306bc33fff8c2035f0d/llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610", size = 41184200, upload_time = "2025-01-20T11:13:20.058Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c6/258801143975a6d09a373f2641237992496e15567b907a4d401839d671b8/llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", size = 30331193, upload_time = "2025-01-20T11:13:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297, upload_time = "2025-01-20T11:13:32.57Z" }, + { url = "https://files.pythonhosted.org/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105, upload_time = "2025-01-20T11:13:38.744Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901, upload_time = "2025-01-20T11:13:46.711Z" }, + { url = "https://files.pythonhosted.org/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247, upload_time = "2025-01-20T11:13:56.159Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380, upload_time = "2025-01-20T11:14:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/89/24/4c0ca705a717514c2092b18476e7a12c74d34d875e05e4d742618ebbf449/llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516", size = 28132306, upload_time = "2025-01-20T11:14:09.035Z" }, + { url = "https://files.pythonhosted.org/packages/01/cf/1dd5a60ba6aee7122ab9243fd614abcf22f36b0437cbbe1ccf1e3391461c/llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e", size = 26201090, upload_time = "2025-01-20T11:14:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1b/656f5a357de7135a3777bd735cc7c9b8f23b4d37465505bd0eaf4be9befe/llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf", size = 42361904, upload_time = "2025-01-20T11:14:22.949Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e1/12c5f20cb9168fb3464a34310411d5ad86e4163c8ff2d14a2b57e5cc6bac/llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc", size = 41184245, upload_time = "2025-01-20T11:14:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/d0/81/e66fc86539293282fd9cb7c9417438e897f369e79ffb62e1ae5e5154d4dd/llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930", size = 30331193, upload_time = "2025-01-20T11:14:38.578Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload_time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload_time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload_time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload_time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload_time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload_time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload_time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload_time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload_time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload_time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload_time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload_time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload_time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload_time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload_time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload_time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload_time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload_time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload_time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload_time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload_time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload_time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload_time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload_time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload_time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload_time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload_time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload_time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload_time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload_time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload_time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload_time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload_time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload_time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload_time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload_time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload_time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload_time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload_time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload_time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload_time = "2024-10-18T15:21:42.784Z" }, ] [[package]] @@ -640,190 +736,269 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669 }, - { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996 }, - { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612 }, - { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258 }, - { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896 }, - { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281 }, - { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488 }, - { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264 }, - { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048 }, - { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111 }, - { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771 }, - { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742 }, - { url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112 }, - { url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931 }, - { url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422 }, - { url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819 }, - { url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782 }, - { url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812 }, - { url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021 }, - { url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782 }, - { url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901 }, - { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864 }, - { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487 }, - { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832 }, +sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335, upload_time = "2025-02-27T19:19:51.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669, upload_time = "2025-02-27T19:18:34.346Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996, upload_time = "2025-02-27T19:18:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612, upload_time = "2025-02-27T19:18:39.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258, upload_time = "2025-02-27T19:18:43.217Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896, upload_time = "2025-02-27T19:18:45.852Z" }, + { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281, upload_time = "2025-02-27T19:18:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488, upload_time = "2025-02-27T19:18:51.436Z" }, + { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264, upload_time = "2025-02-27T19:18:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048, upload_time = "2025-02-27T19:18:56.536Z" }, + { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111, upload_time = "2025-02-27T19:18:59.439Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771, upload_time = "2025-02-27T19:19:01.944Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742, upload_time = "2025-02-27T19:19:04.632Z" }, + { url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112, upload_time = "2025-02-27T19:19:07.59Z" }, + { url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931, upload_time = "2025-02-27T19:19:10.515Z" }, + { url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422, upload_time = "2025-02-27T19:19:12.738Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819, upload_time = "2025-02-27T19:19:15.306Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782, upload_time = "2025-02-27T19:19:17.841Z" }, + { url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812, upload_time = "2025-02-27T19:19:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021, upload_time = "2025-02-27T19:19:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782, upload_time = "2025-02-27T19:19:28.33Z" }, + { url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901, upload_time = "2025-02-27T19:19:31.536Z" }, + { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864, upload_time = "2025-02-27T19:19:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487, upload_time = "2025-02-27T19:19:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832, upload_time = "2025-02-27T19:19:39.431Z" }, ] [[package]] name = "mccabe" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload_time = "2022-01-24T01:14:51.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload_time = "2022-01-24T01:14:49.62Z" }, ] [[package]] name = "metrohash-python" version = "1.1.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/b5/4002d16e71d6a4b1c14a37d204e25aba6508b12915fe0dd07fa703ca0fa1/metrohash-python-1.1.3.3.tar.gz", hash = "sha256:2003c9b2d07514c8228d901e3b32492ae42459e577e946ab91c0ef6eddfd0926", size = 45994 } +sdist = { url = "https://files.pythonhosted.org/packages/95/b5/4002d16e71d6a4b1c14a37d204e25aba6508b12915fe0dd07fa703ca0fa1/metrohash-python-1.1.3.3.tar.gz", hash = "sha256:2003c9b2d07514c8228d901e3b32492ae42459e577e946ab91c0ef6eddfd0926", size = 45994, upload_time = "2022-02-25T01:22:01.063Z" } + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload_time = "2025-02-05T03:50:34.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload_time = "2025-02-05T03:50:17.287Z" }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload_time = "2025-02-05T03:49:51.21Z" }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload_time = "2025-02-05T03:50:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload_time = "2025-02-05T03:49:42.408Z" }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload_time = "2025-02-05T03:49:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload_time = "2025-02-05T03:49:54.581Z" }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload_time = "2025-02-05T03:50:28.25Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload_time = "2025-02-05T03:50:13.411Z" }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload_time = "2025-02-05T03:50:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload_time = "2025-02-05T03:48:48.705Z" }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload_time = "2025-02-05T03:49:03.628Z" }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload_time = "2025-02-05T03:50:00.313Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload_time = "2025-02-05T03:48:55.789Z" }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload_time = "2025-02-05T03:48:44.581Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload_time = "2025-02-05T03:49:25.514Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload_time = "2025-02-05T03:49:57.623Z" }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload_time = "2025-02-05T03:48:52.361Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload_time = "2025-02-05T03:49:11.395Z" }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload_time = "2025-02-05T03:50:08.348Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload_time = "2023-02-04T12:11:27.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload_time = "2023-02-04T12:11:25.002Z" }, +] [[package]] name = "networkx" version = "3.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +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 }, + { 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 = "numba" +version = "0.61.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615, upload_time = "2025-04-09T02:58:07.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/97/c99d1056aed767503c228f7099dc11c402906b42a4757fec2819329abb98/numba-0.61.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2", size = 2775825, upload_time = "2025-04-09T02:57:43.442Z" }, + { url = "https://files.pythonhosted.org/packages/95/9e/63c549f37136e892f006260c3e2613d09d5120672378191f2dc387ba65a2/numba-0.61.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", size = 2778695, upload_time = "2025-04-09T02:57:44.968Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/8740616c8436c86c1b9a62e72cb891177d2c34c2d24ddcde4c390371bf4c/numba-0.61.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", size = 3829227, upload_time = "2025-04-09T02:57:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/fc/06/66e99ae06507c31d15ff3ecd1f108f2f59e18b6e08662cd5f8a5853fbd18/numba-0.61.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", size = 3523422, upload_time = "2025-04-09T02:57:48.222Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a4/2b309a6a9f6d4d8cfba583401c7c2f9ff887adb5d54d8e2e130274c0973f/numba-0.61.2-cp311-cp311-win_amd64.whl", hash = "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", size = 2831505, upload_time = "2025-04-09T02:57:50.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/c6b7b9c615cfa3b98c4c63f4316e3f6b3bbe2387740277006551784218cd/numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", size = 2776626, upload_time = "2025-04-09T02:57:51.857Z" }, + { url = "https://files.pythonhosted.org/packages/92/4a/fe4e3c2ecad72d88f5f8cd04e7f7cff49e718398a2fac02d2947480a00ca/numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", size = 2779287, upload_time = "2025-04-09T02:57:53.658Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2d/e518df036feab381c23a624dac47f8445ac55686ec7f11083655eb707da3/numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", size = 3885928, upload_time = "2025-04-09T02:57:55.206Z" }, + { url = "https://files.pythonhosted.org/packages/10/0f/23cced68ead67b75d77cfcca3df4991d1855c897ee0ff3fe25a56ed82108/numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", size = 3577115, upload_time = "2025-04-09T02:57:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/68/1d/ddb3e704c5a8fb90142bf9dc195c27db02a08a99f037395503bfbc1d14b3/numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", size = 2831929, upload_time = "2025-04-09T02:57:58.45Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f3/0fe4c1b1f2569e8a18ad90c159298d862f96c3964392a20d74fc628aee44/numba-0.61.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:3a10a8fc9afac40b1eac55717cece1b8b1ac0b946f5065c89e00bde646b5b154", size = 2771785, upload_time = "2025-04-09T02:57:59.96Z" }, + { url = "https://files.pythonhosted.org/packages/e9/71/91b277d712e46bd5059f8a5866862ed1116091a7cb03bd2704ba8ebe015f/numba-0.61.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d3bcada3c9afba3bed413fba45845f2fb9cd0d2b27dd58a1be90257e293d140", size = 2773289, upload_time = "2025-04-09T02:58:01.435Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e0/5ea04e7ad2c39288c0f0f9e8d47638ad70f28e275d092733b5817cf243c9/numba-0.61.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdbca73ad81fa196bd53dc12e3aaf1564ae036e0c125f237c7644fe64a4928ab", size = 3893918, upload_time = "2025-04-09T02:58:02.933Z" }, + { url = "https://files.pythonhosted.org/packages/17/58/064f4dcb7d7e9412f16ecf80ed753f92297e39f399c905389688cf950b81/numba-0.61.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f154aaea625fb32cfbe3b80c5456d514d416fcdf79733dd69c0df3a11348e9e", size = 3584056, upload_time = "2025-04-09T02:58:04.538Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/6d3a0f2d3989e62a18749e1e9913d5fa4910bbb3e3311a035baea6caf26d/numba-0.61.2-cp313-cp313-win_amd64.whl", hash = "sha256:59321215e2e0ac5fa928a8020ab00b8e57cda8a97384963ac0dfa4d4e6aa54e7", size = 2831846, upload_time = "2025-04-09T02:58:06.125Z" }, ] [[package]] name = "numpy" -version = "2.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989 }, - { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910 }, - { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490 }, - { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754 }, - { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 }, - { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 }, - { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 }, - { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 }, - { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 }, - { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 }, - { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, - { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, - { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, - { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, - { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, - { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, - { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, - { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, - { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, - { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, - { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, - { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, - { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, - { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, - { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, - { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, - { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, - { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, - { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, - { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, - { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, - { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, - { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, - { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, - { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, - { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, - { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, - { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, - { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, - { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload_time = "2025-04-19T23:27:42.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475, upload_time = "2025-04-19T22:34:24.174Z" }, + { url = "https://files.pythonhosted.org/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474, upload_time = "2025-04-19T22:34:46.578Z" }, + { url = "https://files.pythonhosted.org/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875, upload_time = "2025-04-19T22:34:56.281Z" }, + { url = "https://files.pythonhosted.org/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176, upload_time = "2025-04-19T22:35:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850, upload_time = "2025-04-19T22:35:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306, upload_time = "2025-04-19T22:35:57.573Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767, upload_time = "2025-04-19T22:36:22.245Z" }, + { url = "https://files.pythonhosted.org/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515, upload_time = "2025-04-19T22:36:49.822Z" }, + { url = "https://files.pythonhosted.org/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842, upload_time = "2025-04-19T22:37:01.624Z" }, + { url = "https://files.pythonhosted.org/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071, upload_time = "2025-04-19T22:37:21.098Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633, upload_time = "2025-04-19T22:37:52.4Z" }, + { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123, upload_time = "2025-04-19T22:38:15.058Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817, upload_time = "2025-04-19T22:38:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066, upload_time = "2025-04-19T22:38:35.782Z" }, + { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277, upload_time = "2025-04-19T22:38:57.697Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742, upload_time = "2025-04-19T22:39:22.689Z" }, + { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825, upload_time = "2025-04-19T22:39:45.794Z" }, + { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600, upload_time = "2025-04-19T22:40:13.427Z" }, + { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626, upload_time = "2025-04-19T22:40:25.223Z" }, + { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715, upload_time = "2025-04-19T22:40:44.528Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload_time = "2025-04-19T22:41:16.234Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload_time = "2025-04-19T22:41:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload_time = "2025-04-19T22:41:47.823Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload_time = "2025-04-19T22:41:58.689Z" }, + { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload_time = "2025-04-19T22:42:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload_time = "2025-04-19T22:42:44.433Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload_time = "2025-04-19T22:43:09.928Z" }, + { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload_time = "2025-04-19T22:43:36.983Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload_time = "2025-04-19T22:47:10.523Z" }, + { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload_time = "2025-04-19T22:47:30.253Z" }, + { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload_time = "2025-04-19T22:44:09.251Z" }, + { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload_time = "2025-04-19T22:44:31.383Z" }, + { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload_time = "2025-04-19T22:44:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload_time = "2025-04-19T22:44:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload_time = "2025-04-19T22:45:12.451Z" }, + { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload_time = "2025-04-19T22:45:37.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload_time = "2025-04-19T22:46:01.908Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload_time = "2025-04-19T22:46:28.585Z" }, + { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload_time = "2025-04-19T22:46:39.949Z" }, + { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload_time = "2025-04-19T22:47:00.147Z" }, +] + +[[package]] +name = "optype" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/3c/9d59b0167458b839273ad0c4fc5f62f787058d8f5aed7f71294963a99471/optype-0.9.3.tar.gz", hash = "sha256:5f09d74127d316053b26971ce441a4df01f3a01943601d3712dd6f34cdfbaf48", size = 96143, upload_time = "2025-03-31T17:00:08.392Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/d8/ac50e2982bdc2d3595dc2bfe3c7e5a0574b5e407ad82d70b5f3707009671/optype-0.9.3-py3-none-any.whl", hash = "sha256:2935c033265938d66cc4198b0aca865572e635094e60e6e79522852f029d9e8d", size = 84357, upload_time = "2025-03-31T17:00:06.464Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload_time = "2024-11-08T09:47:47.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload_time = "2024-11-08T09:47:44.722Z" }, ] [[package]] name = "pillow" version = "11.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450 }, - { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550 }, - { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018 }, - { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006 }, - { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773 }, - { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069 }, - { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460 }, - { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304 }, - { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809 }, - { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338 }, - { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918 }, - { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185 }, - { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306 }, - { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121 }, - { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707 }, - { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921 }, - { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523 }, - { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836 }, - { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390 }, - { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309 }, - { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768 }, - { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087 }, - { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 }, - { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 }, - { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 }, - { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 }, - { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 }, - { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 }, - { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 }, - { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 }, - { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 }, - { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 }, - { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 }, - { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 }, - { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 }, - { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 }, - { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 }, - { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 }, - { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 }, - { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 }, - { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 }, - { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, - { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, - { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, - { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734 }, - { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841 }, - { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470 }, - { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013 }, - { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165 }, - { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586 }, - { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751 }, +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload_time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload_time = "2025-04-12T17:47:37.135Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload_time = "2025-04-12T17:47:39.345Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload_time = "2025-04-12T17:47:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload_time = "2025-04-12T17:47:42.912Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload_time = "2025-04-12T17:47:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload_time = "2025-04-12T17:47:46.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload_time = "2025-04-12T17:47:49.255Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload_time = "2025-04-12T17:47:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload_time = "2025-04-12T17:47:54.425Z" }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload_time = "2025-04-12T17:47:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload_time = "2025-04-12T17:47:58.217Z" }, + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload_time = "2025-04-12T17:48:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload_time = "2025-04-12T17:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload_time = "2025-04-12T17:48:04.554Z" }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload_time = "2025-04-12T17:48:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload_time = "2025-04-12T17:48:09.229Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload_time = "2025-04-12T17:48:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload_time = "2025-04-12T17:48:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload_time = "2025-04-12T17:48:15.938Z" }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload_time = "2025-04-12T17:48:17.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload_time = "2025-04-12T17:48:19.655Z" }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload_time = "2025-04-12T17:48:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload_time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload_time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload_time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload_time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload_time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload_time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload_time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload_time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload_time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload_time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload_time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload_time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload_time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload_time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload_time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload_time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload_time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload_time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload_time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload_time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload_time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload_time = "2025-04-12T17:49:08.399Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload_time = "2025-04-12T17:49:46.789Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload_time = "2025-04-12T17:49:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload_time = "2025-04-12T17:49:50.831Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload_time = "2025-04-12T17:49:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload_time = "2025-04-12T17:49:55.164Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload_time = "2025-04-12T17:49:57.171Z" }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload_time = "2025-04-12T17:49:59.628Z" }, ] [[package]] name = "platformdirs" version = "4.3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload_time = "2025-03-19T20:36:10.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload_time = "2025-03-19T20:36:09.038Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload_time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload_time = "2024-04-20T21:34:40.434Z" }, ] [[package]] @@ -835,9 +1010,9 @@ dependencies = [ { name = "pyyaml" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/9b/fd39836a6397fb363446d83075a7b9c2cc562f4c449292e039ed36084376/pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755", size = 402879 } +sdist = { url = "https://files.pythonhosted.org/packages/46/9b/fd39836a6397fb363446d83075a7b9c2cc562f4c449292e039ed36084376/pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755", size = 402879, upload_time = "2021-01-17T20:02:27.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/5f/40d8e90f985a05133a8895fc454c6127ecec3de8b095dd35bba91382f803/pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f", size = 561354 }, + { url = "https://files.pythonhosted.org/packages/ad/5f/40d8e90f985a05133a8895fc454c6127ecec3de8b095dd35bba91382f803/pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f", size = 561354, upload_time = "2021-01-17T20:02:23.696Z" }, ] [[package]] @@ -848,36 +1023,40 @@ 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 } +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 }, + { 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 = "pycodestyle" version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312 } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload_time = "2025-03-29T17:33:30.669Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424 }, + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload_time = "2025-03-29T17:33:29.405Z" }, ] [[package]] -name = "pyflakes" -version = "3.3.2" +name = "pydoclint" +version = "0.6.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175 } +dependencies = [ + { name = "click" }, + { name = "docstring-parser-fork" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/b8/9ab3bb3642e1a87ad5b51276cc6465542935d44c427d75937216b22eadc0/pydoclint-0.6.6.tar.gz", hash = "sha256:22862a8494d05cdf22574d6533f4c47933c0ae1674b0f8b961d6ef42536eaa69", size = 54488, upload_time = "2025-04-16T07:42:23.518Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164 }, + { url = "https://files.pythonhosted.org/packages/bc/05/c714e4dfea5e48140288ee05275e9675fbe1ab1010852ed8c77a388c7ace/pydoclint-0.6.6-py2.py3-none-any.whl", hash = "sha256:7ce8ed36f60f9201bf1c1edacb32c55eb051af80fdd7304480c6419ee0ced43c", size = 48713, upload_time = "2025-04-16T07:42:22.023Z" }, ] [[package]] name = "pygments" version = "2.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload_time = "2025-01-06T17:26:30.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload_time = "2025-01-06T17:26:25.553Z" }, ] [[package]] @@ -893,18 +1072,34 @@ dependencies = [ { name = "platformdirs" }, { name = "tomlkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/a7/113d02340afb9dcbb0c8b25454e9538cd08f0ebf3e510df4ed916caa1a89/pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a", size = 1519586 } +sdist = { url = "https://files.pythonhosted.org/packages/69/a7/113d02340afb9dcbb0c8b25454e9538cd08f0ebf3e510df4ed916caa1a89/pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a", size = 1519586, upload_time = "2025-03-20T11:25:38.207Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/21/9537fc94aee9ec7316a230a49895266cf02d78aa29b0a2efbc39566e0935/pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", size = 522462 }, + { url = "https://files.pythonhosted.org/packages/31/21/9537fc94aee9ec7316a230a49895266cf02d78aa29b0a2efbc39566e0935/pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", size = 522462, upload_time = "2025-03-20T11:25:36.13Z" }, +] + +[[package]] +name = "pynndescent" +version = "0.5.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "llvmlite" }, + { name = "numba" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/58/560a4db5eb3794d922fe55804b10326534ded3d971e1933c1eef91193f5e/pynndescent-0.5.13.tar.gz", hash = "sha256:d74254c0ee0a1eeec84597d5fe89fedcf778593eeabe32c2f97412934a9800fb", size = 2975955, upload_time = "2024-06-17T15:48:32.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/53/d23a97e0a2c690d40b165d1062e2c4ccc796be458a1ce59f6ba030434663/pynndescent-0.5.13-py3-none-any.whl", hash = "sha256:69aabb8f394bc631b6ac475a1c7f3994c54adf3f51cd63b2730fefba5771b949", size = 56850, upload_time = "2024-06-17T15:48:31.184Z" }, ] [[package]] name = "pyparsing" version = "3.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload_time = "2025-03-25T05:01:28.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload_time = "2025-03-25T05:01:24.908Z" }, ] [[package]] @@ -914,9 +1109,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714 } +sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714, upload_time = "2025-01-21T18:02:00.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131 }, + { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131, upload_time = "2025-01-21T18:01:58.927Z" }, ] [[package]] @@ -929,9 +1124,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload_time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload_time = "2025-03-02T12:54:52.069Z" }, ] [[package]] @@ -942,9 +1137,23 @@ dependencies = [ { name = "coverage" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 } +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload_time = "2025-04-05T14:07:51.592Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload_time = "2025-04-05T14:07:49.641Z" }, +] + +[[package]] +name = "pytest-mypy" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "mypy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/50/3ce149b469e27848c1dc354553b17774f9dde0140625f5a4130bd21e1052/pytest_mypy-1.0.1.tar.gz", hash = "sha256:3f5fcaff75c80dccc6b68cf5ecc28e1bbe71e95309469eb7a28bf408ce55c074", size = 15975, upload_time = "2025-04-02T19:31:16.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 }, + { url = "https://files.pythonhosted.org/packages/bf/93/25ed3c02e15c4ef1b04cbda7c708ffc5da755986aaacfb48db1f9e84a996/pytest_mypy-1.0.1-py3-none-any.whl", hash = "sha256:ad7133c9b92c802e032f2596590ebede7eea7c418e61d60d5cdd571b55c72056", size = 8701, upload_time = "2025-04-02T19:31:14.914Z" }, ] [[package]] @@ -954,44 +1163,44 @@ 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 } +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 }, + { 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 = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload_time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload_time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload_time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload_time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload_time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload_time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload_time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload_time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload_time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload_time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload_time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload_time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload_time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload_time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload_time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload_time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload_time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload_time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload_time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload_time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload_time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload_time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload_time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload_time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload_time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload_time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload_time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload_time = "2024-08-06T20:33:04.33Z" }, ] [[package]] @@ -1004,18 +1213,18 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" }, ] [[package]] name = "roman-numerals-py" version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload_time = "2025-02-22T07:34:54.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload_time = "2025-02-22T07:34:52.422Z" }, ] [[package]] @@ -1032,24 +1241,24 @@ dependencies = [ { name = "scipy" }, { name = "tifffile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594 } +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/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116 }, - { 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 }, + { 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]] @@ -1062,27 +1271,27 @@ dependencies = [ { name = "scipy" }, { name = "threadpoolctl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620 }, - { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234 }, - { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155 }, - { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069 }, - { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809 }, - { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516 }, - { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837 }, - { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728 }, - { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700 }, - { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613 }, - { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001 }, - { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360 }, - { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004 }, - { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776 }, - { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865 }, - { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804 }, - { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530 }, - { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852 }, - { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256 }, +sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312, upload_time = "2025-01-10T08:07:55.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620, upload_time = "2025-01-10T08:06:16.675Z" }, + { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234, upload_time = "2025-01-10T08:06:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155, upload_time = "2025-01-10T08:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069, upload_time = "2025-01-10T08:06:32.515Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809, upload_time = "2025-01-10T08:06:35.514Z" }, + { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516, upload_time = "2025-01-10T08:06:40.009Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837, upload_time = "2025-01-10T08:06:43.305Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728, upload_time = "2025-01-10T08:06:47.618Z" }, + { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700, upload_time = "2025-01-10T08:06:50.888Z" }, + { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613, upload_time = "2025-01-10T08:06:54.115Z" }, + { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001, upload_time = "2025-01-10T08:06:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360, upload_time = "2025-01-10T08:07:01.556Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004, upload_time = "2025-01-10T08:07:06.931Z" }, + { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776, upload_time = "2025-01-10T08:07:11.715Z" }, + { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865, upload_time = "2025-01-10T08:07:16.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804, upload_time = "2025-01-10T08:07:20.385Z" }, + { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530, upload_time = "2025-01-10T08:07:23.675Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852, upload_time = "2025-01-10T08:07:26.817Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256, upload_time = "2025-01-10T08:07:31.084Z" }, ] [[package]] @@ -1092,71 +1301,83 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 }, - { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 }, - { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 }, - { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523 }, - { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 }, - { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 }, - { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 }, - { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 }, - { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 }, - { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 }, - { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 }, - { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 }, - { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 }, - { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 }, - { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 }, - { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 }, - { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 }, - { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 }, - { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587 }, - { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266 }, - { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768 }, - { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719 }, - { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 }, - { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 }, - { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 }, - { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 }, - { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 }, - { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286 }, - { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634 }, - { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179 }, - { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412 }, - { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 }, - { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 }, - { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 }, - { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 }, - { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, +sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316, upload_time = "2025-02-17T00:42:24.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651, upload_time = "2025-02-17T00:30:31.09Z" }, + { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038, upload_time = "2025-02-17T00:30:40.219Z" }, + { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518, upload_time = "2025-02-17T00:30:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523, upload_time = "2025-02-17T00:30:56.002Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547, upload_time = "2025-02-17T00:31:07.599Z" }, + { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077, upload_time = "2025-02-17T00:31:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657, upload_time = "2025-02-17T00:31:22.041Z" }, + { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857, upload_time = "2025-02-17T00:31:29.836Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654, upload_time = "2025-02-17T00:31:43.65Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184, upload_time = "2025-02-17T00:31:50.623Z" }, + { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558, upload_time = "2025-02-17T00:31:56.721Z" }, + { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211, upload_time = "2025-02-17T00:32:03.042Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260, upload_time = "2025-02-17T00:32:07.847Z" }, + { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095, upload_time = "2025-02-17T00:32:14.565Z" }, + { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371, upload_time = "2025-02-17T00:32:21.411Z" }, + { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390, upload_time = "2025-02-17T00:32:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276, upload_time = "2025-02-17T00:32:37.431Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317, upload_time = "2025-02-17T00:32:45.47Z" }, + { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587, upload_time = "2025-02-17T00:32:53.196Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266, upload_time = "2025-02-17T00:32:59.318Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768, upload_time = "2025-02-17T00:33:04.091Z" }, + { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719, upload_time = "2025-02-17T00:33:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195, upload_time = "2025-02-17T00:33:15.352Z" }, + { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404, upload_time = "2025-02-17T00:33:22.21Z" }, + { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011, upload_time = "2025-02-17T00:33:29.446Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406, upload_time = "2025-02-17T00:33:39.019Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243, upload_time = "2025-02-17T00:34:51.024Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286, upload_time = "2025-02-17T00:33:47.62Z" }, + { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634, upload_time = "2025-02-17T00:33:54.131Z" }, + { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179, upload_time = "2025-02-17T00:33:59.948Z" }, + { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412, upload_time = "2025-02-17T00:34:06.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867, upload_time = "2025-02-17T00:34:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009, upload_time = "2025-02-17T00:34:19.55Z" }, + { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159, upload_time = "2025-02-17T00:34:26.724Z" }, + { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566, upload_time = "2025-02-17T00:34:34.512Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705, upload_time = "2025-02-17T00:34:43.619Z" }, +] + +[[package]] +name = "scipy-stubs" +version = "1.15.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "optype" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/ee/0c3e93545b53d3b22e662fbe251c3e61c2b14742f296f36708eff4a2898c/scipy_stubs-1.15.2.2.tar.gz", hash = "sha256:0137d907d75381d2eda4f6af5b1d3211759cb193a0eadf5195716fb0b01ca3cb", size = 275755, upload_time = "2025-04-07T20:59:18.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/1a/3eba813584e398d589e1d4e0dac0cf822ce9e25b28cb2d1f0012d137c968/scipy_stubs-1.15.2.2-py3-none-any.whl", hash = "sha256:f02fe66124b58bce5f0897ecd48d0e79226a999cc4e6984a9472520c20b8e4b6", size = 459133, upload_time = "2025-04-07T20:59:16.664Z" }, ] [[package]] name = "setuptools" version = "78.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827, upload_time = "2025-03-25T22:49:35.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 }, + { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108, upload_time = "2025-03-25T22:49:33.13Z" }, ] [[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 } +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 }, + { 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 = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699, upload_time = "2021-11-16T18:38:38.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002, upload_time = "2021-11-16T18:38:34.792Z" }, ] [[package]] @@ -1182,9 +1403,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 } +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 }, + { 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]] @@ -1194,9 +1415,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload_time = "2023-04-14T08:10:22.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343 }, + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload_time = "2023-04-14T08:10:20.844Z" }, ] [[package]] @@ -1208,18 +1429,18 @@ dependencies = [ { name = "sphinx" }, { 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 } +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 }, + { 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 } +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 }, + { 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]] @@ -1233,9 +1454,9 @@ dependencies = [ { name = "setuptools", marker = "python_full_version >= '3.12'" }, { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/ce/054a8ec04063f9a27772fea7188f796edbfa382e656d3b76428323861f0e/sphinxcontrib_bibtex-2.6.3.tar.gz", hash = "sha256:7c790347ef1cb0edf30de55fc324d9782d085e89c52c2b8faafa082e08e23946", size = 117177 } +sdist = { url = "https://files.pythonhosted.org/packages/c1/ce/054a8ec04063f9a27772fea7188f796edbfa382e656d3b76428323861f0e/sphinxcontrib_bibtex-2.6.3.tar.gz", hash = "sha256:7c790347ef1cb0edf30de55fc324d9782d085e89c52c2b8faafa082e08e23946", size = 117177, upload_time = "2024-09-12T14:23:44.662Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/49/c23f9493c0a5d5881fb7ed3002e87708454fef860aa96a48e755d27bf6f0/sphinxcontrib_bibtex-2.6.3-py3-none-any.whl", hash = "sha256:ff016b738fcc867df0f75c29e139b3b2158d26a2c802db27963cb128be3b75fb", size = 40340 }, + { url = "https://files.pythonhosted.org/packages/8e/49/c23f9493c0a5d5881fb7ed3002e87708454fef860aa96a48e755d27bf6f0/sphinxcontrib_bibtex-2.6.3-py3-none-any.whl", hash = "sha256:ff016b738fcc867df0f75c29e139b3b2158d26a2c802db27963cb128be3b75fb", size = 40340, upload_time = "2024-09-12T14:23:43.593Z" }, ] [[package]] @@ -1248,27 +1469,27 @@ dependencies = [ { name = "sphinx" }, { name = "sphinxcontrib-runcmd" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/9e/8ac54a6a3e7a00339f417568899b64a0c0d622429db73cc1a28c8122c8e2/sphinxcontrib.datatemplates-0.11.0.tar.gz", hash = "sha256:793222e803430076341509cc167f8d715830b05e418c885313101d60fd442557", size = 30996 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/9e/8ac54a6a3e7a00339f417568899b64a0c0d622429db73cc1a28c8122c8e2/sphinxcontrib.datatemplates-0.11.0.tar.gz", hash = "sha256:793222e803430076341509cc167f8d715830b05e418c885313101d60fd442557", size = 30996, upload_time = "2023-12-21T14:59:10.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/8d/7a7dd95ad1eedec8dc770570c8b1f3dc1d13357383635607b6629ccf329c/sphinxcontrib.datatemplates-0.11.0-py3-none-any.whl", hash = "sha256:88d02f5edab32b88211ebb72a90553e3676a5737877bad1de412f84058ac282e", size = 12493 }, + { url = "https://files.pythonhosted.org/packages/d4/8d/7a7dd95ad1eedec8dc770570c8b1f3dc1d13357383635607b6629ccf329c/sphinxcontrib.datatemplates-0.11.0-py3-none-any.whl", hash = "sha256:88d02f5edab32b88211ebb72a90553e3676a5737877bad1de412f84058ac282e", size = 12493, upload_time = "2023-12-21T14:59:07.96Z" }, ] [[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 } +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 }, + { 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 } +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 }, + { 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]] @@ -1278,27 +1499,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331 } +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 }, + { 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 } +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 }, + { 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 } +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 }, + { 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]] @@ -1308,27 +1529,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/03/6eb30814c9839f36131284a46ec9fc39d7bd356078648bc7125d5d1c05e8/sphinxcontrib-runcmd-0.2.0.tar.gz", hash = "sha256:3551c389d9c5fe82d693c7222feb9658b1a1a5a1abcb0063e8385e5528c64c76", size = 5376 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/03/6eb30814c9839f36131284a46ec9fc39d7bd356078648bc7125d5d1c05e8/sphinxcontrib-runcmd-0.2.0.tar.gz", hash = "sha256:3551c389d9c5fe82d693c7222feb9658b1a1a5a1abcb0063e8385e5528c64c76", size = 5376, upload_time = "2018-11-08T20:19:34.32Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/d9/67a79080b5d9fcb367470af9e525a9c53122e95744665de09462dcd676d8/sphinxcontrib_runcmd-0.2.0-py2.py3-none-any.whl", hash = "sha256:7b739b68e27210b4c7c12ba16e5b3da7b313c49991401f896d29bea0f0771934", size = 6043 }, + { url = "https://files.pythonhosted.org/packages/83/d9/67a79080b5d9fcb367470af9e525a9c53122e95744665de09462dcd676d8/sphinxcontrib_runcmd-0.2.0-py2.py3-none-any.whl", hash = "sha256:7b739b68e27210b4c7c12ba16e5b3da7b313c49991401f896d29bea0f0771934", size = 6043, upload_time = "2018-11-08T20:19:33.17Z" }, ] [[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 } +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 }, + { 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 = "threadpoolctl" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload_time = "2025-03-13T13:49:23.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload_time = "2025-03-13T13:49:21.846Z" }, ] [[package]] @@ -1338,18 +1559,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/54/d5ebe66a9de349b833e570e87bdbd9eec76ec54bd505c24b0591a15783ad/tifffile-2025.3.30.tar.gz", hash = "sha256:3cdee47fe06cd75367c16bc3ff34523713156dae6cd498e3a392e5b39a51b789", size = 366039 } +sdist = { url = "https://files.pythonhosted.org/packages/3c/54/d5ebe66a9de349b833e570e87bdbd9eec76ec54bd505c24b0591a15783ad/tifffile-2025.3.30.tar.gz", hash = "sha256:3cdee47fe06cd75367c16bc3ff34523713156dae6cd498e3a392e5b39a51b789", size = 366039, upload_time = "2025-03-30T04:45:30.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/be/10d23cfd4078fbec6aba768a357eff9e70c0b6d2a07398425985c524ad2a/tifffile-2025.3.30-py3-none-any.whl", hash = "sha256:0ed6eee7b66771db2d1bfc42262a51b01887505d35539daef118f4ff8c0f629c", size = 226837 }, + { url = "https://files.pythonhosted.org/packages/6e/be/10d23cfd4078fbec6aba768a357eff9e70c0b6d2a07398425985c524ad2a/tifffile-2025.3.30-py3-none-any.whl", hash = "sha256:0ed6eee7b66771db2d1bfc42262a51b01887505d35539daef118f4ff8c0f629c", size = 226837, upload_time = "2025-03-30T04:45:29Z" }, ] [[package]] name = "tomlkit" version = "0.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885, upload_time = "2024-08-14T08:19:41.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955, upload_time = "2024-08-14T08:19:40.05Z" }, ] [[package]] @@ -1367,9 +1588,9 @@ dependencies = [ { name = "pyproject-api" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/87/692478f0a194f1cad64803692642bd88c12c5b64eee16bf178e4a32e979c/tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52", size = 196255 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/87/692478f0a194f1cad64803692642bd88c12c5b64eee16bf178e4a32e979c/tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52", size = 196255, upload_time = "2025-03-27T15:13:37.519Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/38/33348de6fc4b1afb3d76d8485c8aecbdabcfb3af8da53d40c792332e2b37/tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c", size = 172420 }, + { url = "https://files.pythonhosted.org/packages/f9/38/33348de6fc4b1afb3d76d8485c8aecbdabcfb3af8da53d40c792332e2b37/tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c", size = 172420, upload_time = "2025-03-27T15:13:35.703Z" }, ] [[package]] @@ -1381,43 +1602,81 @@ dependencies = [ { name = "tox" }, { name = "uv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/3a/3e445f25978a716ba6674f33f687d9336d0312086a277a778a5e9e9220d7/tox_uv-1.25.0.tar.gz", hash = "sha256:59ee5e694c41fef7bbcf058f22a5f9b6a8509698def2ea60c08554f4e36b9fcc", size = 21114 } +sdist = { url = "https://files.pythonhosted.org/packages/5d/3a/3e445f25978a716ba6674f33f687d9336d0312086a277a778a5e9e9220d7/tox_uv-1.25.0.tar.gz", hash = "sha256:59ee5e694c41fef7bbcf058f22a5f9b6a8509698def2ea60c08554f4e36b9fcc", size = 21114, upload_time = "2025-02-21T16:37:51.796Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a7/f5c29e0e6faaccefcab607f672b176927144e9412c8183d21301ea2a6f6c/tox_uv-1.25.0-py3-none-any.whl", hash = "sha256:50cfe7795dcd49b2160d7d65b5ece8717f38cfedc242c852a40ec0a71e159bf7", size = 16431, upload_time = "2025-02-21T16:37:49.657Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload_time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload_time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload_time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload_time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "umap-learn" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numba" }, + { name = "numpy" }, + { name = "pynndescent" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/d4/9ed627905f7993349671283b3c5bf2d9f543ef79229fa1c7e01324eb900c/umap-learn-0.5.7.tar.gz", hash = "sha256:b2a97973e4c6ffcebf241100a8de589a4c84126a832ab40f296c6d9fcc5eb19e", size = 92680, upload_time = "2024-10-28T18:05:57.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a7/f5c29e0e6faaccefcab607f672b176927144e9412c8183d21301ea2a6f6c/tox_uv-1.25.0-py3-none-any.whl", hash = "sha256:50cfe7795dcd49b2160d7d65b5ece8717f38cfedc242c852a40ec0a71e159bf7", size = 16431 }, + { url = "https://files.pythonhosted.org/packages/3c/8f/671c0e1f2572ba625cbcc1faeba9435e00330c3d6962858711445cf1e817/umap_learn-0.5.7-py3-none-any.whl", hash = "sha256:6a7e0be2facfa365a5ed6588447102bdbef32a0ef449535c25c97ea7e680073c", size = 88815, upload_time = "2024-10-28T18:05:55.333Z" }, ] [[package]] name = "urllib3" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" }, ] [[package]] name = "uv" version = "0.6.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/eb/07bc000a3c05372448b63c45da98630c532ec4e059d848488c3e774d017a/uv-0.6.14.tar.gz", hash = "sha256:a117466f307d164a74444949cc94ec4328ec880fb489cbaa7df324dab14c5c98", size = 3134567 } +sdist = { url = "https://files.pythonhosted.org/packages/e5/eb/07bc000a3c05372448b63c45da98630c532ec4e059d848488c3e774d017a/uv-0.6.14.tar.gz", hash = "sha256:a117466f307d164a74444949cc94ec4328ec880fb489cbaa7df324dab14c5c98", size = 3134567, upload_time = "2025-04-09T21:57:01.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/bf/3e87dec7728b249458967f39a301376cb776e559c90261c1dac963686dc3/uv-0.6.14-py3-none-linux_armv6l.whl", hash = "sha256:c775e5d7a80ff43cb88856bbdcd838918d5ac3dc362414317e6bbaeb615fff98", size = 16228143 }, - { url = "https://files.pythonhosted.org/packages/24/b2/111e1ea40453d93c849f36a67397b51d9b458e6e598c3629ffe76d11b490/uv-0.6.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2578f6f8cdbcc036ffad1043f9f66ade3ac0babf29def6abd9eefd4a7c6621cb", size = 16273279 }, - { url = "https://files.pythonhosted.org/packages/72/89/e7fc8a047f08234cc26d1e37e5f573887744205d087f8e8e6f3d0feb04ce/uv-0.6.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9fc8fe58871b4fe02a863b05b8b1b25ef1b6c60d4d224e85338f5c2be0ab4f0e", size = 15115451 }, - { url = "https://files.pythonhosted.org/packages/20/1e/72ac3d1e0805d3b49b0a4de46483489ea1989827440f42b0cfb444cdc67f/uv-0.6.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2fb2cd7f6aae21b81474b0051d30e7ed939a9a71714948c47f58b0e7acdd2a80", size = 15540456 }, - { url = "https://files.pythonhosted.org/packages/fd/47/5aeb7fb80c673bc28ccf3ab99e376b1cd92eac41af6b9b48c0e38b114c54/uv-0.6.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d6ca3f99c1a6c1c430ae8f451133fb4e8c3a22f661c257425402a5d9430bb797", size = 15979820 }, - { url = "https://files.pythonhosted.org/packages/1f/44/c3ad856473f2ef5f22c865a73a0a37ee82d11fcca78ae82f5ac895a7023a/uv-0.6.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed41877b679e0a1af9ab65427d829b87a81b499017e59c70756d4ba02ca43fcb", size = 16650494 }, - { url = "https://files.pythonhosted.org/packages/7a/f6/8a1245530c282d470909db78cf56831693c58b90d9b819e35aa2d85fbbe8/uv-0.6.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe9b4361b1c8055301b715fdd94d94eb512053dc4545fec40d3fe3657f655987", size = 17505028 }, - { url = "https://files.pythonhosted.org/packages/a5/70/0806268440651e2ad1b3542af42b800e20bb7e43050a9ca78f3d1eb4c660/uv-0.6.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998b67bb1cebbe044fc2c5cb251c29cffc56f62a6d55719d6f4e960461d6edad", size = 17245854 }, - { url = "https://files.pythonhosted.org/packages/2a/3a/0da9780868626466d8c4977fb02d1b0daa80e6f7504d7b662cae3fb4af3d/uv-0.6.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d433925db6e2ef46047b68962d136ff2ef17a7b5609168615f19e60674232c9", size = 21584756 }, - { url = "https://files.pythonhosted.org/packages/eb/fd/21a82b78173be1a2ea20f4f55154e7252bd80d21ed60b9bbbc0e2047b8d0/uv-0.6.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36aaeb00a70a10f748e16c7a1fc410862e2ba905806e7e9dfbc3e64596309404", size = 16878847 }, - { url = "https://files.pythonhosted.org/packages/6c/9a/7c84650ae9fb801ecc848d49dcba201243989d9234fe3ec4a4e935ff21c0/uv-0.6.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:11779beb3bd1f92814bc8d8cd350d5228e8f9198cca2f52138b53030a4061d93", size = 15810089 }, - { url = "https://files.pythonhosted.org/packages/0b/b3/efcbd3a2d298801109b24feee655bb80fe4178aa6bf68e49664c48b342b2/uv-0.6.14-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:bf1ec103cf9a0850f03935dc6a93cacc680fa2c90c3b41cfc10da311afab8f5b", size = 15962056 }, - { url = "https://files.pythonhosted.org/packages/3f/53/c92c894cb34e9578c2e6dc195bcd4eb0a140dd57c96a60207d847521a902/uv-0.6.14-py3-none-musllinux_1_1_i686.whl", hash = "sha256:955e36c98a438a249e178988d4f13b1bb831eb57264d73c459f171b5afd7b023", size = 16255226 }, - { url = "https://files.pythonhosted.org/packages/df/eb/38bc37856691d53008bf094d03d9e7ab0c2927523a3901c83e152e7c9915/uv-0.6.14-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2d534e7dc1299c8b53eb7b4c7575e4f0933673ea8b1275d3f3022f5670e311db", size = 17005225 }, - { url = "https://files.pythonhosted.org/packages/d8/fe/087d5193603e16bc5f67556d94cf8fa8634785c5863cccdec825f14e9a4c/uv-0.6.14-py3-none-win32.whl", hash = "sha256:7cdf3c8d927b07d4eaffc44809eb57523d449705f10dabbdd6f34f7bdfc7d5fe", size = 16131231 }, - { url = "https://files.pythonhosted.org/packages/40/17/33c5c1503c35c874932d4a21ec10a55051e3695dba12b7de700bcfad0cca/uv-0.6.14-py3-none-win_amd64.whl", hash = "sha256:012f46bef6909209c4a6749e4019eb755ba762d37d7ceaaf76da9cb4b7f771e9", size = 17628508 }, - { url = "https://files.pythonhosted.org/packages/77/09/163062d439ddc0d89e527ae0e631abf1f7781b183442d8823c48af368f5d/uv-0.6.14-py3-none-win_arm64.whl", hash = "sha256:7465081b4d0b213d0055ccb48de7fe546b5cf0853c6d3601115760760634f6d8", size = 16387232 }, + { url = "https://files.pythonhosted.org/packages/6b/bf/3e87dec7728b249458967f39a301376cb776e559c90261c1dac963686dc3/uv-0.6.14-py3-none-linux_armv6l.whl", hash = "sha256:c775e5d7a80ff43cb88856bbdcd838918d5ac3dc362414317e6bbaeb615fff98", size = 16228143, upload_time = "2025-04-09T21:56:03.883Z" }, + { url = "https://files.pythonhosted.org/packages/24/b2/111e1ea40453d93c849f36a67397b51d9b458e6e598c3629ffe76d11b490/uv-0.6.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2578f6f8cdbcc036ffad1043f9f66ade3ac0babf29def6abd9eefd4a7c6621cb", size = 16273279, upload_time = "2025-04-09T21:56:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/72/89/e7fc8a047f08234cc26d1e37e5f573887744205d087f8e8e6f3d0feb04ce/uv-0.6.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9fc8fe58871b4fe02a863b05b8b1b25ef1b6c60d4d224e85338f5c2be0ab4f0e", size = 15115451, upload_time = "2025-04-09T21:56:12.061Z" }, + { url = "https://files.pythonhosted.org/packages/20/1e/72ac3d1e0805d3b49b0a4de46483489ea1989827440f42b0cfb444cdc67f/uv-0.6.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2fb2cd7f6aae21b81474b0051d30e7ed939a9a71714948c47f58b0e7acdd2a80", size = 15540456, upload_time = "2025-04-09T21:56:14.971Z" }, + { url = "https://files.pythonhosted.org/packages/fd/47/5aeb7fb80c673bc28ccf3ab99e376b1cd92eac41af6b9b48c0e38b114c54/uv-0.6.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d6ca3f99c1a6c1c430ae8f451133fb4e8c3a22f661c257425402a5d9430bb797", size = 15979820, upload_time = "2025-04-09T21:56:17.949Z" }, + { url = "https://files.pythonhosted.org/packages/1f/44/c3ad856473f2ef5f22c865a73a0a37ee82d11fcca78ae82f5ac895a7023a/uv-0.6.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed41877b679e0a1af9ab65427d829b87a81b499017e59c70756d4ba02ca43fcb", size = 16650494, upload_time = "2025-04-09T21:56:21.403Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f6/8a1245530c282d470909db78cf56831693c58b90d9b819e35aa2d85fbbe8/uv-0.6.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe9b4361b1c8055301b715fdd94d94eb512053dc4545fec40d3fe3657f655987", size = 17505028, upload_time = "2025-04-09T21:56:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/0806268440651e2ad1b3542af42b800e20bb7e43050a9ca78f3d1eb4c660/uv-0.6.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998b67bb1cebbe044fc2c5cb251c29cffc56f62a6d55719d6f4e960461d6edad", size = 17245854, upload_time = "2025-04-09T21:56:28.052Z" }, + { url = "https://files.pythonhosted.org/packages/2a/3a/0da9780868626466d8c4977fb02d1b0daa80e6f7504d7b662cae3fb4af3d/uv-0.6.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d433925db6e2ef46047b68962d136ff2ef17a7b5609168615f19e60674232c9", size = 21584756, upload_time = "2025-04-09T21:56:31.886Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/21a82b78173be1a2ea20f4f55154e7252bd80d21ed60b9bbbc0e2047b8d0/uv-0.6.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36aaeb00a70a10f748e16c7a1fc410862e2ba905806e7e9dfbc3e64596309404", size = 16878847, upload_time = "2025-04-09T21:56:35.628Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9a/7c84650ae9fb801ecc848d49dcba201243989d9234fe3ec4a4e935ff21c0/uv-0.6.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:11779beb3bd1f92814bc8d8cd350d5228e8f9198cca2f52138b53030a4061d93", size = 15810089, upload_time = "2025-04-09T21:56:39.175Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/efcbd3a2d298801109b24feee655bb80fe4178aa6bf68e49664c48b342b2/uv-0.6.14-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:bf1ec103cf9a0850f03935dc6a93cacc680fa2c90c3b41cfc10da311afab8f5b", size = 15962056, upload_time = "2025-04-09T21:56:42.236Z" }, + { url = "https://files.pythonhosted.org/packages/3f/53/c92c894cb34e9578c2e6dc195bcd4eb0a140dd57c96a60207d847521a902/uv-0.6.14-py3-none-musllinux_1_1_i686.whl", hash = "sha256:955e36c98a438a249e178988d4f13b1bb831eb57264d73c459f171b5afd7b023", size = 16255226, upload_time = "2025-04-09T21:56:45.402Z" }, + { url = "https://files.pythonhosted.org/packages/df/eb/38bc37856691d53008bf094d03d9e7ab0c2927523a3901c83e152e7c9915/uv-0.6.14-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2d534e7dc1299c8b53eb7b4c7575e4f0933673ea8b1275d3f3022f5670e311db", size = 17005225, upload_time = "2025-04-09T21:56:48.609Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fe/087d5193603e16bc5f67556d94cf8fa8634785c5863cccdec825f14e9a4c/uv-0.6.14-py3-none-win32.whl", hash = "sha256:7cdf3c8d927b07d4eaffc44809eb57523d449705f10dabbdd6f34f7bdfc7d5fe", size = 16131231, upload_time = "2025-04-09T21:56:52.117Z" }, + { url = "https://files.pythonhosted.org/packages/40/17/33c5c1503c35c874932d4a21ec10a55051e3695dba12b7de700bcfad0cca/uv-0.6.14-py3-none-win_amd64.whl", hash = "sha256:012f46bef6909209c4a6749e4019eb755ba762d37d7ceaaf76da9cb4b7f771e9", size = 17628508, upload_time = "2025-04-09T21:56:55.444Z" }, + { url = "https://files.pythonhosted.org/packages/77/09/163062d439ddc0d89e527ae0e631abf1f7781b183442d8823c48af368f5d/uv-0.6.14-py3-none-win_arm64.whl", hash = "sha256:7465081b4d0b213d0055ccb48de7fe546b5cf0853c6d3601115760760634f6d8", size = 16387232, upload_time = "2025-04-09T21:56:58.872Z" }, ] [[package]] @@ -1429,7 +1688,7 @@ dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 } +sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945, upload_time = "2025-03-31T16:33:29.185Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 }, + { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461, upload_time = "2025-03-31T16:33:26.758Z" }, ] diff --git a/tests/linters/.flake8 b/tests/linters/.flake8 deleted file mode 100644 index 10b985e..0000000 --- a/tests/linters/.flake8 +++ /dev/null @@ -1,16 +0,0 @@ - -# Flake8 configuration file -[flake8] - -# Ignores the following rules: -# R0902 Too many instance attributes -# R0913 Too many arguments -# R0914 Too many local variables -# W503 Line-break before binary operator -ignore = R0902,R0913,R0914,W503 - -# Exclude the following directories from linting -exclude=.venv,.git,.tox,build,dist,docs,*egg,*.ini - -# Maximum number of characters on a single line -max-line-length = 120 diff --git a/tests/linters/.mypy.ini b/tests/linters/.mypy.ini new file mode 100644 index 0000000..ac57cdd --- /dev/null +++ b/tests/linters/.mypy.ini @@ -0,0 +1,40 @@ + +# General MyPy configuration +[mypy] + +# Use strict checks for all files, which turns on all optional checks +strict = true + +# When sub-classing from a class for which no type information is available, MyPy assumes that the type of the base class is Any; although, ideally, +# we would not allow this, there are packages that do not have type stubs or a py.typed marker, therefore, we need to allow this +disallow_subclassing_any = False + +# Disables the "type-abstract" error, which is too opinionated for our use case; the error is raised when assigning an abstract type to a variable or +# parameter that is of type "type[]"; in CoRelAy we often use this to specify the data type of something; MyPy assumes that type +# variables will be used to instantiate objects from, so it disallows users from assigning abstract types to it; this is of course sensible, but it +# ignores the fact, that there are legitimate use cases for this: in the case of CoRelAy we only use these types to check that the specified data type +# is correct and it may be valid to assign an abstract type in this case, because we are not instantiating the type, but only using it to check the +# type of the data; I guess, the MyPy's argument is, that they cannot trace if the type will be used to instantiate an object or not, so they disallow +# this in general; however, I believe, that this is too strict and would result in a meaningful runtime error; statically disallowing things that have +# legitimate use cases, only because they may lead to runtime errors, is not a good idea; therefore, we disable this check +disable_error_code = type-abstract + +# The H5PY package does not have type stubs or a py.typed marker +[mypy-h5py.*] +ignore_missing_imports = true + +# The MetroHash package does not have type stubs or a py.typed marker +[mypy-metrohash.*] +ignore_missing_imports = true + +# The Sphinx PyTeX plugin package does not have type stubs or a py.typed marker +[mypy-pybtex.*] +ignore_missing_imports = true + +# The SciKit Learn package does not have type stubs or a py.typed marker +[mypy-sklearn.*] +ignore_missing_imports = true + +# The Sphinx package does not have type stubs or a py.typed marker +[mypy-sphinx.*] +ignore_missing_imports = true diff --git a/tests/linters/.pycodestyle b/tests/linters/.pycodestyle new file mode 100644 index 0000000..07cc9c0 --- /dev/null +++ b/tests/linters/.pycodestyle @@ -0,0 +1,7 @@ + +# The PyCodeStyle configuration +[pycodestyle] + +# Sets the maximum line length for both code and docstrings to 150 characters +max-line-length = 150 +max-doc-length = 150 diff --git a/tests/linters/.pydoclint.toml b/tests/linters/.pydoclint.toml new file mode 100644 index 0000000..37c60e4 --- /dev/null +++ b/tests/linters/.pydoclint.toml @@ -0,0 +1,15 @@ + +# The PyDocLint configuration +[tool.pydoclint] + +# The ViRelAy backend REST API project uses the Google style docstrings +style = 'google' + +# Normally, the class docstring would contain the documentation for the __init__ function, but we prefer to have a separate docstring for the __init__ +allow-init-docstring = true + +# Normally, the class docstring would contain the documentation for the class attributes, but we prefer to have separate docstrings for the attributes +check-class-attributes = false + +# Short docstring should not be skipped, as the only reason they are short may be that they are missing sections +skip-checking-short-docstrings = false diff --git a/tests/linters/.pylintrc b/tests/linters/.pylintrc index 1e2ec4b..170361b 100644 --- a/tests/linters/.pylintrc +++ b/tests/linters/.pylintrc @@ -1,519 +1,40 @@ -[MASTER] -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-whitelist= +# Configures general settings for Pylint +[MAIN] -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - # custom disables - too-many-instance-attributes, - too-many-arguments, - too-many-locals, - too-few-public-methods, - too-many-statements, - redefined-outer-name, - abstract-method, - too-many-public-methods, - unspecified-encoding, - - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _, - n, - c, - h, - w, - z, - x, - y, - f, - fp, - fd, - io, - re, - at, - dc, - v0, - - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -#variable-rgx= - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=120 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[LOGGING] - -# Format style used to check logging format string. `old` means using % -# formatting, while `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[STRING] - -# This flag controls whether the implicit-str-concat-in-sequence should -# generate a warning on implicit string concatenation in sequences defined over -# several lines. -check-str-concat-over-line-jumps=no - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls +# Ignores the version file, which is automatically generated by the build system and not checked into the version control system +ignore=version.py +# Configures design-related rules for the Pylint static code analysis tool; these rules help enforce certain design principles and constraints in the +# codebase, such maximum and minimum numbers of method/function arguments, class attributes, and class methods [DESIGN] -# Maximum number of arguments for function / method. -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement. -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 +# Maximum number of arguments for a function/method +max-args=10 -# Maximum number of return / yield for function / method body. -max-returns=6 +# Maximum number of positional arguments for a function/method +max-positional-arguments=10 -# Maximum number of statements in function / method body. -max-statements=50 +# Maximum number of locals for a function/method body +max-locals=40 -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 +# Maximum number of statements in a function/method body +max-statements=75 +# Maximum number of public methods for a class (see R0904) +max-public-methods=30 -[IMPORTS] +# Maximum number of return/yield statements in a function/method body +max-returns=10 -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no +# Minimum number of public methods for a class (see R0903) +min-public-methods=0 -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analyzed. -analyse-fallback-blocks=no -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[EXCEPTIONS] +# Configures format-related rules for the Pylint static code analysis tool; these rules help enforce certain formatting conventions in the codebase, +# such as the maximum number of characters on a single line and the maximum number of lines in a module +[FORMAT] -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception +# Maximum number of characters on a single line +max-line-length=150 diff --git a/tests/linters/cspell/.cspell.json b/tests/linters/cspell/.cspell.json index 6d0c0ed..782430d 100644 --- a/tests/linters/cspell/.cspell.json +++ b/tests/linters/cspell/.cspell.json @@ -82,20 +82,31 @@ "agglo", "Anders", "Anson", + "arccos", "automodule", "autosummary", "basepython", + "Batchelder", "bcde", "bibfiles", + "braycurtis", + "buildinfo", + "cblock", "CFF", + "cheb", + "cheby", + "chebychev", + "chebyshev", "Chormai", "chr5tphr", "chrstphr", + "cityblock", "clusterings", "codezombiech", "cooperativity", "copybutton", "CoRelAy", + "coveragerc", "csints", "datatemplates", "DBSCAN", @@ -105,6 +116,7 @@ "doctree", "docz", "dtypes", + "dunder", "dvipng", "edcba", "eigenstuff", @@ -124,6 +136,7 @@ "getrev", "hashval", "hdbscan", + "hellinger", "histogramdd", "htmlcov", "htmlvalidate", @@ -134,8 +147,13 @@ "iterhash", "iterread", "iterwrite", + "jacc", + "jaccard", + "jensenshannon", "keepdims", "kmeans", + "kulczynski", + "kulsinski", "Laplacians", "Lapuschkin", "lecode", @@ -144,11 +162,13 @@ "linestart", "linestop", "ller", + "mahal", "mathjax", "membertype", "metacls", "metrohash", "mgrid", + "minkowski", "modindex", "modname", "modpath", @@ -162,6 +182,7 @@ "nosignatures", "notest", "nsamples", + "nsri", "orcid", "overgeneral", "parseable", @@ -171,6 +192,7 @@ "pearsonr", "Pickler", "Plugboards", + "pnorm", "posargs", "prepreprocess", "pybtex", @@ -182,11 +204,19 @@ "regionref", "ritwickdey", "rodolphebarbanneau", + "rogerstanimoto", + "russellrao", + "russelrao", "Samek", + "seuclidean", + "showlocals", "skimage", "SNE", + "sokalmichener", + "sokalsneath", "sphinxcontrib", "Sprincl", + "sqeuclid", "sqeuclidean", "squareform", "SSIM", @@ -209,7 +239,9 @@ "vimrc", "vsicons", "wayou", + "wminkowski", "Wojciech", + "wordnet", "xticks", "yticks", "yzhang", diff --git a/tests/linters/cspell/package.json b/tests/linters/cspell/package.json index fb8d91c..456639a 100644 --- a/tests/linters/cspell/package.json +++ b/tests/linters/cspell/package.json @@ -1,6 +1,6 @@ { "name": "@corelay/cspell-config", - "version": "0.5.0", + "version": "0.3.0", "description": "The CSpell configuration for CoRelAy.", "author": { "name": "David Neumann", diff --git a/tests/linters/markdownlint/.markdownlint.yaml b/tests/linters/markdownlint/.markdownlint.yaml new file mode 100644 index 0000000..a008bb3 --- /dev/null +++ b/tests/linters/markdownlint/.markdownlint.yaml @@ -0,0 +1,19 @@ + +# Uses the default configuration for all rules +default: true + +# The line length rule is not used in this project, as breaking the lines manually takes a lot of work and always has to be redone after updating the +# document, furthermore, many editors allow the auto-breaking of lines to fit the text inside the window, which is optimal for prose +line-length: false + +# In this project, inline HTML is used to improve the style of the documents +no-inline-html: false + +# The first H1 heading is usually inside a table, which formats the title of the document, therefore, the first line of a document cannot contain the +# heading +first-line-heading: false + +# In the documentation, the lines of all shell scripts are prefixed with a "$" prompt to signify that the line is being executed in the terminal; by +# default Markdownlint only allows a "$" prompt if it is followed by console output; in this project we do not show any command output and therefore, +# the rule is disabled +commands-show-output: false diff --git a/tests/linters/markdownlint/package-lock.json b/tests/linters/markdownlint/package-lock.json new file mode 100644 index 0000000..932fde8 --- /dev/null +++ b/tests/linters/markdownlint/package-lock.json @@ -0,0 +1,1174 @@ +{ + "name": "@virelay/markdownlint-config", + "version": "0.6.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@virelay/markdownlint-config", + "version": "0.6.1", + "license": "AGPL-3.0-or-later", + "dependencies": { + "markdownlint": "^0.37.4", + "markdownlint-cli2": "^0.17.2" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", + "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdownlint": { + "version": "0.37.4", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.37.4.tgz", + "integrity": "sha512-u00joA/syf3VhWh6/ybVFkib5Zpj2e5KB/cfCei8fkSRuums6nyisTWGqjTWIOFoFwuXoTBQQiqlB4qFKp8ncQ==", + "license": "MIT", + "dependencies": { + "markdown-it": "14.1.0", + "micromark": "4.0.1", + "micromark-core-commonmark": "2.0.2", + "micromark-extension-directive": "3.0.2", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.0", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.17.2.tgz", + "integrity": "sha512-XH06ZOi8wCrtOSSj3p8y3yJzwgzYOSa7lglNyS3fP05JPRzRGyjauBb5UvlLUSCGysMmULS1moxdRHHudV+g/Q==", + "license": "MIT", + "dependencies": { + "globby": "14.0.2", + "js-yaml": "4.1.0", + "jsonc-parser": "3.3.1", + "markdownlint": "0.37.4", + "markdownlint-cli2-formatter-default": "0.0.5", + "micromatch": "4.0.8" + }, + "bin": { + "markdownlint-cli2": "markdownlint-cli2-bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2-formatter-default": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.5.tgz", + "integrity": "sha512-4XKTwQ5m1+Txo2kuQ3Jgpo/KmnG+X90dWt4acufg6HVGadTUG5hzHF/wssp9b5MBYOMCnZ9RMPaU//uHsszF8Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + }, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tests/linters/markdownlint/package.json b/tests/linters/markdownlint/package.json new file mode 100644 index 0000000..9e8a317 --- /dev/null +++ b/tests/linters/markdownlint/package.json @@ -0,0 +1,18 @@ +{ + "name": "@corelay/markdownlint-config", + "version": "0.3.0", + "description": "The MarkdownLint configuration for CoRelAy.", + "author": { + "name": "David Neumann", + "email": "david.neumann@lecode.de" + }, + "license": "AGPL-3.0-or-later", + "private": true, + "dependencies": { + "markdownlint": "^0.37.4", + "markdownlint-cli2": "^0.17.2" + }, + "scripts": { + "markdownlint": "npx markdownlint-cli2 --config .markdownlint.yaml '../../../**/*.md' '#../../../**/node_modules' '#../../../**/.venv' '#../../../**/.tox' '#../../../**/.pytest_cache'" + } +} diff --git a/tests/tox.ini b/tests/tox.ini deleted file mode 100644 index 4cc683c..0000000 --- a/tests/tox.ini +++ /dev/null @@ -1,97 +0,0 @@ - -# Core tox configuration -[tox] - -# A list of environments that will be run by default when running tox without specifying any environment -envlist = py311,py312,py313,coverage,pylint,flake8,docs - -# Base configuration for test environments that tox will fallback to for missing values; this avoids having to repeat the same configuration for the -# unit test environments py311, py312, and py313 over and over again -[testenv] -setenv = - COVERAGE_FILE = .coverage.{envname} -commands = - uv run pytest \ - --cov "../source/corelay" \ - --cov-config "tox.ini" \ - --cov-append \ - {posargs:unit_tests} - -# A test environment that combines the coverage data from all runs of the unit tests with the different Python versions and generates a single report -[testenv:coverage] -setenv = - COVERAGE_FILE = /.coverage -depends = py311,py312,py313 -commands = - uv run coverage combine - uv run coverage report -m - -# A test environment that will build the documentation using Sphinx -[testenv:docs] -basepython = python3.13.3 -commands = - uv run sphinx-build \ - --color \ - -W \ - --keep-going \ - -d "../docs/doctree" \ - -b html \ - "../docs/source" \ - "../docs/build" \ - {posargs} - -# A test environment that will run the Flake8 meta-linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples -[testenv:flake8] -basepython = python3.13.3 -commands = - uv run flake8 \ - --config "linters/.flake8" \ - "../source/corelay" \ - "unit_tests" \ - "../docs/source/conf.py" \ - "../example" - {posargs} - -# A test environment that will run the PyLint linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples -[testenv:pylint] -basepython = python3.13.3 -commands = - uv run pylint \ - --rcfile="linters/.pylintrc" \ - --output-format="parseable" \ - "../source/corelay" \ - "unit_tests" \ - "../docs/source/conf.py" \ - "../example" - -# The configuration for the PyTest test runner -[pytest] -testpaths = unit_tests -addopts = -ra -l -filterwarnings = error - -# The general configuration for the PyTest coverage plugin -[coverage:run] - -# Specifies that branch coverage should be measured -branch = true - -# Causes the PyTest coverage plugin to append the machine name, process ID, and a random number to the data file name to simplify collecting data from -# many processes; this is done because we are running the unit tests using multiple Python versions -parallel = true - -# The configuration for the PyTest coverage plugin when generating the coverage report -[coverage:report] - -# Specifies that files that have 100% coverage should be omitted from the report, so that we can focus on the files that need more testing -skip_covered = true - -# Specifies that the report should include a list of the lines that were not covered by the unit tests -show_missing = true - -# Since the coverage data can be collected from multiple different installations of CoRelAy, the Coverage tool needs to know which files are -# equivalent; this configuration section contains named lists (in our case only a single list called "source"), where two file paths are considered -# equivalent and combined when running the "coverage combine" command when they are in the same list; here we specify that the files in directories -# called */source/corelay and */.tox/*/lib/python*/site-packages/corelay are equivalent -[coverage:paths] -source = */source/corelay,*/.tox/*/lib/python*/site-packages/corelay diff --git a/tests/unit_tests/.coveragerc b/tests/unit_tests/.coveragerc new file mode 100644 index 0000000..d49bb8f --- /dev/null +++ b/tests/unit_tests/.coveragerc @@ -0,0 +1,31 @@ + +# The general configuration for the PyTest coverage plugin +[run] + +# Specifies that branch coverage should be measured +branch = true + +# Causes the PyTest coverage plugin to append the machine name, process ID, and a random number to the data file name to simplify collecting data from +# many processes; this is done because we are running the unit tests using multiple Python versions +parallel = true + +# The version.py file is generated by the build system, therefore, it is omitted from the coverage measurement and reporting +omit=*/corelay/version.py + + +# The configuration for the PyTest coverage plugin when generating the coverage report +[report] + +# Specifies that files that have 100% coverage should be omitted from the report, so that we can focus on the files that need more testing +skip_covered = true + +# Specifies that the report should include a list of the lines that were not covered by the unit tests +show_missing = true + + +# Since the coverage data can be collected from multiple different installations of CoRelAy, the Coverage tool needs to know which files are +# equivalent; this configuration section contains named lists (in our case only a single list called "source"), where two file paths are considered +# equivalent and combined when running the "coverage combine" command when they are in the same list; here we specify that the files in directories +# called */source/corelay and */.tox/*/lib/python*/site-packages/corelay are equivalent +[paths] +source = */source/corelay,*/.tox/*/lib/python*/site-packages/corelay diff --git a/tests/unit_tests/.pytest.ini b/tests/unit_tests/.pytest.ini new file mode 100644 index 0000000..dd79e01 --- /dev/null +++ b/tests/unit_tests/.pytest.ini @@ -0,0 +1,12 @@ + +# The configuration for the PyTest test runner +[pytest] + +# Specifies the directories that PyTest should search for test files when no files or directories are specified on the command line +testpaths = ../tests/unit_tests + +# Specifies extra command line arguments that should be passed to PyTest when it is run +# -ra: Shows extra test summary info as specified by characters: (f) failed, (E) error, (s) skipped, (x) failed, (X) passed, (p) passed, (P) passed +# with output, (a) all except passed (p/P), or (A) all +# --showlocals: Show local variables in tracebacks +addopts = -ra --showlocals diff --git a/tests/unit_tests/corelay/__init__.py b/tests/unit_tests/corelay/__init__.py index e69de29..8b5ec7c 100644 --- a/tests/unit_tests/corelay/__init__.py +++ b/tests/unit_tests/corelay/__init__.py @@ -0,0 +1 @@ +"""A package that contains unit tests for the ``corelay`` package.""" diff --git a/tests/unit_tests/corelay/io/__init__.py b/tests/unit_tests/corelay/io/__init__.py index e69de29..4d97bbc 100644 --- a/tests/unit_tests/corelay/io/__init__.py +++ b/tests/unit_tests/corelay/io/__init__.py @@ -0,0 +1 @@ +"""A sub-package that contains unit tests for the ``corelay.io`` sub-package.""" diff --git a/tests/unit_tests/corelay/io/test_storage.py b/tests/unit_tests/corelay/io/test_storage.py index 4ed6bee..48c8378 100644 --- a/tests/unit_tests/corelay/io/test_storage.py +++ b/tests/unit_tests/corelay/io/test_storage.py @@ -1,154 +1,187 @@ -"""Test io functionalities +"""A module that contains unit tests for the ``corelay.io.storage`` module.""" -""" from io import BytesIO +from pathlib import Path -import pytest -import numpy as np import h5py +import numpy +import pytest +from numpy.typing import NDArray from corelay import io -from corelay.io.storage import HashedHDF5 +from corelay.io.storage import DataStorageBase, HashedHDF5 -@pytest.fixture -def data(): - """Return random data with shape (10, 2). +@pytest.fixture(name='data', scope='module') +def get_data_fixture() -> NDArray[numpy.float64]: + """A fixture that produces random test data with shape (10, 2). + Returns: + NDArray[numpy.float64]: Returns random test data with shape (10, 2). """ - return np.random.rand(10, 2) + + return numpy.random.rand(10, 2) -@pytest.fixture -def param_values(): - """Return dict with param1=1 and param2='string'. +@pytest.fixture(name='parameter_values', scope='module') +def get_parameter_values_fixture() -> dict[str, int | str]: + """A fixture that produces a parameter values dictionary with param1=1 and param2='string'. + Returns: + dict[str, int | str]: Returns a dictionary with param1=1 and param2='string'. """ - return dict(param1=1, param2='string') + + return {'param1': 1, 'param2': 'string'} class TestHashedHDF5: - """Test class for HashedHDF5""" + """Contains unit tests for the ``HashedHDF5`` class.""" + @staticmethod - def test_write_array(): - """Writing a numpy array should raise no Exceptions""" - with BytesIO() as buf, h5py.File(buf, 'w') as fd: - group = fd.require_group('hashed') - data_out = np.random.normal(size=5) - iobj = HashedHDF5(group) - iobj.write(data_out=data_out, data_in=1, meta=1) + def test_write_array() -> None: + """Tests that writing a NumPy array raises no exceptions""" + + with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: + group = hdf5_file.require_group('hashed') + data_out = numpy.random.normal(size=5) + io_object = HashedHDF5(group) + io_object.write(data_out=data_out, data_in=1, meta=1) @staticmethod - def test_write_tuple(): - """Writing a tuple of numpy arrays should raise no Exceptions""" - with BytesIO() as buf, h5py.File(buf, 'w') as fd: - group = fd.require_group('hashed') - data_out = (np.random.normal(size=5),) * 2 - iobj = HashedHDF5(group) - iobj.write(data_out=data_out, data_in=1, meta=1) + def test_write_tuple() -> None: + """Tests that writing a tuple of NumPy arrays raises no exceptions""" + + with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: + group = hdf5_file.require_group('hashed') + data_out = (numpy.random.normal(size=5),) * 2 + io_object = HashedHDF5(group) + io_object.write(data_out=data_out, data_in=1, meta=1) @staticmethod - def test_write_unsupported(): - """Writing an unsupported type should raise a TypeError""" - with BytesIO() as buf, h5py.File(buf, 'w') as fd: - group = fd.require_group('hashed') + def test_write_unsupported() -> None: + """Tests that writing an unsupported type raises a ``TypeError``""" + + with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: + group = hdf5_file.require_group('hashed') data_out = 'adfg' - iobj = HashedHDF5(group) + io_object = HashedHDF5(group) with pytest.raises(TypeError): - iobj.write(data_out=data_out, data_in=1, meta=1) + io_object.write(data_out=data_out, data_in=1, meta=1) @staticmethod - def test_read_array(): - """Reading after writing should return the same data""" - with BytesIO() as buf, h5py.File(buf, 'w') as fd: - group = fd.require_group('hashed') - data_out = np.random.normal(size=5) - iobj = HashedHDF5(group) - iobj.write(data_out=data_out, data_in=1, meta=1) - loaded = iobj.read(data_in=1, meta=1) - assert (data_out == loaded).all() + def test_read_array() -> None: + """Tests that reading data that was previously written returns the same data""" + + with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: + group = hdf5_file.require_group('hashed') + data_out = numpy.random.normal(size=5) + io_object = HashedHDF5(group) + io_object.write(data_out=data_out, data_in=1, meta=1) + data_read = io_object.read(data_in=1, meta=1) + assert (data_out == data_read).all() @staticmethod - def test_read_tuple(): - """Reading after writing should return the same data""" - with BytesIO() as buf, h5py.File(buf, 'w') as fd: - group = fd.require_group('hashed') - data_out = (np.random.normal(size=5),) * 2 - iobj = HashedHDF5(group) - iobj.write(data_out=data_out, data_in=1, meta=1) - loaded = iobj.read(data_in=1, meta=1) - assert all((out == load).all() for out, load in zip(data_out, loaded)) + def test_read_tuple() -> None: + """Tests that reading data that was previously written returns the same data""" + with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: + group = hdf5_file.require_group('hashed') + data_out = (numpy.random.normal(size=5),) * 2 + io_object = HashedHDF5(group) + io_object.write(data_out=data_out, data_in=1, meta=1) + data_read = io_object.read(data_in=1, meta=1) + assert all((out == load).all() for out, load in zip(data_out, data_read)) -@pytest.mark.parametrize("storage", [io.HDF5Storage, io.PickleStorage]) -def test_data_storage_at_functionality(storage, tmp_path): - """Test HDF5storage and PickleStorage using .at(data_key) functionality. +@pytest.mark.parametrize('data_storage_type', [io.HDF5Storage, io.PickleStorage]) +def test_data_storage_at_functionality(data_storage_type: type[io.HDF5Storage | io.PickleStorage], tmp_path: Path) -> None: + """Tests the reading and writing of data from ``HDF5storage`` and ``PickleStorage`` data storage containers using the `at(data_key)` method. + + Args: + data_storage_type (type[io.HDF5Storage | io.PickleStorage]): The storage class to be tested. + tmp_path (Path): A temporary path for testing. """ - # pylint: disable=protected-access - test_path = tmp_path / "test.file" - with storage(test_path, mode='a') as data_storage: - c = data_storage.at(data_key='param_values') - assert c.data_key == 'param_values', "Data key was not changed." + + with data_storage_type(tmp_path / 'test.file', mode='a') as data_storage: + + data_storage_copy = data_storage.at(data_key='param_values') + assert data_storage_copy.data_key == 'param_values', 'Data key was not changed.' # type: ignore[attr-defined] + + # Should raise a TypeError because a non-existing key was set with pytest.raises(TypeError): - # raises TypeError because non existing key was set. data_storage.at(data_key='param_values', key='key') + with pytest.raises(TypeError): data_storage.at(data_key=1) + # Should raise a TypeError, since we try to access a data_key internally without it being ever set with pytest.raises(TypeError): - # raise TypeError, since we try to access data_key internally without it being ever set data_storage.at().exists() data_storage.at(data_key='param_values').at(data_key='data') -@pytest.mark.parametrize("storage", [io.HDF5Storage, io.PickleStorage]) -def test_data_storage(storage, tmp_path, data, param_values): - """Test HDF5storage and PickleStorage writing and loading functionalities. - +@pytest.mark.parametrize('data_storage_type', [io.HDF5Storage, io.PickleStorage]) +def test_data_storage( + data_storage_type: type[io.HDF5Storage | io.PickleStorage], + tmp_path: Path, + data: NDArray[numpy.float64], + parameter_values: dict[str, int | str] +) -> None: + """Tests the reading and writing of data to and from ``HDF5storage`` and ``PickleStorage`` data storage containers. + + Args: + data_storage_type (type[io.HDF5Storage | io.PickleStorage]): The storage class to be tested. + tmp_path (Path): A temporary path for testing. + data (NDArray[numpy.float64]): Random test data with shape (10, 2). + parameter_values (dict[str, int | str]): A dictionary with param1=1 and param2='string'. """ - # Test writing - test_path = tmp_path / "test.file" - data_storage = storage(test_path, mode='w') - data_storage.at(data_key='data').write(data) - data_storage.close() - - # Test reading - data_storage = storage(test_path, mode='r') - ret_data = data_storage.at(data_key='data').read() - np.testing.assert_equal(ret_data, data) - data_storage.close() - - # Test same with context manager - with storage(test_path, mode='a') as data_storage: - data_storage.at(data_key='param_values').write(param_values) - - with storage(test_path, mode='r') as data_storage: - assert 'param_values' in data_storage - assert 'data' in data_storage - assert data_storage.at(data_key='param_values').exists() - assert data_storage.at(data_key='data').exists() - assert not data_storage.at(data_key='non_existing').exists() - ret_param_values = data_storage['param_values'] - ret_param_values_2 = data_storage.at(data_key='param_values').read() + + # Tests writing data + test_path = tmp_path / 'test.file' + data_storage_writer: DataStorageBase = data_storage_type(test_path, mode='w') + data_storage_writer.at(data_key='data').write(data) + data_storage_writer.close() + + # Tests reading data + data_storage_reader: DataStorageBase = data_storage_type(test_path, mode='r') + returned_data = data_storage_reader.at(data_key='data').read() + numpy.testing.assert_equal(returned_data, data) + data_storage_reader.close() + + # Tests writing data with a context manager + with data_storage_type(test_path, mode='a') as data_storage_writer: + data_storage_writer.at(data_key='param_values').write(parameter_values) + + # Tests reading data with a context manager + with data_storage_type(test_path, mode='r') as data_storage_reader: + assert 'param_values' in data_storage_reader + assert 'data' in data_storage_reader + assert data_storage_reader.at(data_key='param_values').exists() + assert data_storage_reader.at(data_key='data').exists() + assert not data_storage_reader.at(data_key='non_existing').exists() + first_returned_parameter_values = data_storage_reader['param_values'] + second_returned_parameter_values = data_storage_reader.at(data_key='param_values').read() with pytest.raises(io.NoDataSource): - _ = data_storage['non_existing'] + _ = data_storage_reader['non_existing'] - np.testing.assert_equal(ret_param_values, param_values) - np.testing.assert_equal(ret_param_values_2, param_values) + numpy.testing.assert_equal(first_returned_parameter_values, parameter_values) + numpy.testing.assert_equal(second_returned_parameter_values, parameter_values) - with storage(test_path, mode='a') as data_storage: - data_storage.at(data_key='new_entry/data').write(data) - assert data_storage.at(data_key='new_entry/data').exists() - ret_data = data_storage.at(data_key='new_entry/data').read() - np.testing.assert_equal(ret_data, data) + with data_storage_type(test_path, mode='a') as data_storage_reader_writer: + data_storage_reader_writer.at(data_key='new_entry/data').write(data) + assert data_storage_reader_writer.at(data_key='new_entry/data').exists() + returned_data = data_storage_reader_writer.at(data_key='new_entry/data').read() + numpy.testing.assert_equal(returned_data, data) -def test_no_storage(data): - """Test NoStorage instance that raises error when reading and writing. +def test_no_storage(data: NDArray[numpy.float64]) -> None: + """Tests the reading and writing of data to and from a ``NoStorage`` data storage container that raises exceptions when reading and writing. + Args: + data (NDArray[numpy.float64]): Random test data with shape (10, 2). """ + data_storage = io.NoStorage() with pytest.raises(io.NoDataSource): data_storage.read() diff --git a/tests/unit_tests/corelay/pipeline/__init__.py b/tests/unit_tests/corelay/pipeline/__init__.py index e69de29..b35cd23 100644 --- a/tests/unit_tests/corelay/pipeline/__init__.py +++ b/tests/unit_tests/corelay/pipeline/__init__.py @@ -0,0 +1 @@ +"""A sub-package that contains unit tests for the ``corelay.pipeline`` sub-package.""" diff --git a/tests/unit_tests/corelay/pipeline/test_base.py b/tests/unit_tests/corelay/pipeline/test_base.py index 4c3f72c..8346f8b 100644 --- a/tests/unit_tests/corelay/pipeline/test_base.py +++ b/tests/unit_tests/corelay/pipeline/test_base.py @@ -1,45 +1,76 @@ -"""Test module for corelay/pipeline/base.py""" +"""A module that contains unit tests for the ``corelay.io.base`` module.""" + from collections import OrderedDict from types import FunctionType +from typing import Annotated, Any import pytest -from corelay.processor.base import Processor, Param, FunctionProcessor +from corelay.base import Param from corelay.pipeline.base import Pipeline, Task +from corelay.processor.base import Processor, FunctionProcessor + +@pytest.fixture(name='processor_type', scope='module') +def get_processor_type_fixture() -> type[Processor]: + """A fixture that produces a custom ``Processor`` type. + + Returns: + type[Processor]: Returns a custom ``Processor`` type. + """ -@pytest.fixture(scope='module') -def processor_type(): - """Fixture for custom Processor type""" class MyProcessor(Processor): - """Custom Processor""" - param_1 = Param(object) - param_2 = Param(object) + """A custom ``Processor`` type.""" + + param_1: Annotated[Any, Param(Any)] + param_2: Annotated[Any, Param(Any)] + + def function(self, data: Any) -> Any: + """Multiplies the input data by 2. + + Args: + data (Any): The input data that is to be processed. + + Returns: + Any: Returns the processed data. + """ - def function(self, data): - """Double input""" return data * 2 return MyProcessor -@pytest.fixture(scope='module') -def pipeline_type(processor_type): - """Fixture for custom Pipeline type""" +@pytest.fixture(name='pipeline_type', scope='module') +def get_pipeline_type_fixture(processor_type: type[Processor]) -> type[Pipeline]: + """A fixture that produces a custom ``Pipeline`` type. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the custom ``Pipeline`` type. + + Returns: + type[Pipeline]: Returns a custom ``Pipeline`` type. + """ + class MyPipeline(Pipeline): - """Custom Pipeline""" + """A custom ``Pipeline`` type.""" + task_1 = Task(FunctionProcessor, lambda self, x: x + 3, is_output=False, bind_method=True) task_2 = Task(processor_type, processor_type(), is_output=True) return MyPipeline -@pytest.fixture(scope='module') -# pylint: disable=unused-argument -def pipeline_type_multi(processor_type): - """Fixture for custom Pipeline type with multiple outputs""" +@pytest.fixture(name='pipeline_with_multiple_outputs_type', scope='module') +def get_pipeline_with_multiple_outputs_type_fixture() -> type[Pipeline]: + """A fixture that produces a custom ``Pipeline`` type with multiple outputs. + + Returns: + type[Pipeline]: Returns a custom ``Pipeline`` type with multiple outputs. + """ + class MyPipeline(Pipeline): - """Custom pipeline with multiple outputs""" + """A custom ``Pipeline`` type with multiple outputs.""" + task_1 = Task(FunctionProcessor, lambda self, x: x + 2, is_output=True, bind_method=True) task_2 = Task(FunctionProcessor, lambda self, x: x * 2, is_output=True, bind_method=True) @@ -47,124 +78,209 @@ class MyPipeline(Pipeline): class TestTask: - """Test class for Task""" + """Contains unit tests for the ``Task`` class.""" + @staticmethod - def test_instantiation_default(): - """instantiation without any arguments should succeed""" + def test_instantiation_default() -> None: + """Tests that the instantiation of a ``Task`` without any arguments succeeds.""" + Task() @staticmethod - def test_instantiation_arguments(processor_type): - """Instantiating with correct arguments should succeed""" + def test_instantiation_arguments(processor_type: type[Processor]) -> None: + """Tests that the instantiation of a ``Task`` with correct arguments succeeds. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the ``Task``. + """ + Task(proc_type=processor_type, default=processor_type(), is_output=True) @staticmethod - def test_proc_type_no_proc(): - """Instantiating with a proc_type which is not a subclass for Processor should raise a TypeError""" + def test_proc_type_no_proc() -> None: + """Tests that the instantiation of a ``Task`` with a ``proc_type`` that is not a sub-class of ``Processor`` raises a ''TypeError''.""" + with pytest.raises(TypeError): - Task(proc_type=FunctionType, default=(lambda x: x)) + Task(proc_type=FunctionType, default=lambda x: x) # type: ignore[arg-type] @staticmethod - def test_default_no_proc(): - """Instantiating with a default value not of type Processor should fail""" + def test_default_no_proc() -> None: + """Tests that the instantiation of a ``Task`` with a default value that is not of type ``Processor`` fails.""" + with pytest.raises(TypeError): - Task(proc_type=Processor, default='bla') + Task(default='bla') # type: ignore[arg-type] @staticmethod - def test_proc_type_default_type_mismatch(processor_type): - """Instantiating with a default value not of type proc_type should raise a TypeError""" + def test_proc_type_default_type_mismatch(processor_type: type[Processor]) -> None: + """Tests that the instantiation of a ``Task`` with a default value that is not of type ``proc_type`` raises a ``TypeError``. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the ``Task``. + """ + with pytest.raises(TypeError): - Task(proc_type=processor_type, default=(lambda x: x)) + Task(proc_type=processor_type, default=lambda x: x) @staticmethod - def test_default_function_identity(): - """The default function of a FunctionProcessor should be the identity""" + def test_default_function_identity() -> None: + """Tests that the default function of a ``FunctionProcessor`` is the identity function.""" + + # For some reason, PyLint does not recognize that the type of the default value is Processor and/or that Processor is callable task = Task() - # pylint: disable=not-callable - assert task.default(42) == 42 + assert task.default is not None + assert task.default(42) == 42 # pylint: disable=not-callable @staticmethod - def test_assigned_default(processor_type): - """Assigning a default Processor value should succeed""" + def test_assigned_default(processor_type: type[Processor]) -> None: + """Tests that assigning a default ``Processor`` value succeeds. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the ``Task``. + """ + + # For some reason, PyLint does not recognize that the type of the default value is Processor and/or that Processor is callable task = Task(proc_type=processor_type, default=processor_type()) - # pylint: disable=not-callable - assert task.default(5) == 10 + assert task.default is not None + assert task.default(5) == 10 # pylint: disable=not-callable class TestPipeline: - """Test class for Pipeline""" + """Contains unit tests for the ``Pipeline`` class.""" + @staticmethod - def test_instantiation_base(): - """instantiation of the base class without any arguments should succeed""" + def test_instantiation_base() -> None: + """Tests that the instantiation of the base class ``Pipeline`` without any arguments succeeds.""" + Pipeline() @staticmethod - def test_instantiation_default(pipeline_type): - """instantiation of a custom subclass without any arguments should succeed""" + def test_instantiation_default(pipeline_type: type[Pipeline]) -> None: + """Tests that the instantiation of a custom sub-class of ``Pipeline`` without any arguments succeeds. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + """ + pipeline_type() @staticmethod - def test_instantiation_arguments(pipeline_type, processor_type): - """instantiation with correct arguments should succeed""" + def test_instantiation_arguments(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: + """Tests that the instantiation of a custom sub-class of ``Pipeline`` with the correct arguments succeeds. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + pipeline_type(task_1=lambda x: x + 2, task_2=processor_type()) @staticmethod - def test_default_call(pipeline_type): - """Calling with all defaults in place should succeed""" + def test_default_call(pipeline_type: type[Pipeline]) -> None: + """Tests that running a pipeline with all defaults in place succeeds. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + """ + pipeline = pipeline_type() pipeline(0) @staticmethod - def test_default_call_no_input(pipeline_type): - """Calling without an input should raise a TypeError""" + def test_default_call_no_input(pipeline_type: type[Pipeline]) -> None: + """Tests that running a pipeline without an input raises a ``TypeError``. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + """ + pipeline = pipeline_type() with pytest.raises(TypeError): - pipeline() + pipeline() # type: ignore[call-arg] @staticmethod - def test_default_call_output(pipeline_type): - """Default Processors should output correctly""" + def test_default_call_output(pipeline_type: type[Pipeline]) -> None: + """Tests that running a pipeline with the default processors returns the correct output. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + """ + pipeline = pipeline_type() - out = pipeline(1) - assert out == 8 + output = pipeline(1) + + assert output == 8 @staticmethod - def test_default_call_output_multiple(pipeline_type_multi): - """Default Processors should output correctly with multiple outputs""" - pipeline = pipeline_type_multi() - out = pipeline(0) - assert out == (2, 4) + def test_default_call_output_multiple(pipeline_with_multiple_outputs_type: type[Pipeline]) -> None: + """Tests that processors that have multiple outputs, correctly output a tuple containing the outputs. + + Args: + pipeline_with_multiple_outputs_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + """ + + pipeline = pipeline_with_multiple_outputs_type() + output = pipeline(0) + + assert output == (2, 4) @staticmethod - def test_default_param_values(pipeline_type, processor_type): - """Default Parameter values should be assigned correctly""" - proc = processor_type(is_output=False) - pipeline = pipeline_type(task_2=proc) - assert not pipeline.task_2.is_output + def test_default_param_values(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: + """Tests that the default parameter values are assigned correctly. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + processor = processor_type(is_output=False) + pipeline = pipeline_type(task_2=processor) + + assert not pipeline.task_2.is_output # type: ignore[attr-defined] @staticmethod - def test_checkpoint_processes(pipeline_type, processor_type): - """Collecting all processors relevant to a checkpoint should succeed""" - proc_1 = FunctionProcessor(function=lambda x: x + 5, is_checkpoint=False) - proc_2 = processor_type(is_checkpoint=True) - pipeline = pipeline_type(task_1=proc_1, task_2=proc_2) - assert pipeline.checkpoint_processes() == OrderedDict(task_2=proc_2) + def test_checkpoint_processes(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: + """Tests that collecting all processors relevant to a checkpoint succeeds. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + first_processor = FunctionProcessor(processing_function=lambda x: x + 5, is_checkpoint=False) + second_processor = processor_type(is_checkpoint=True) + pipeline = pipeline_type(task_1=first_processor, task_2=second_processor) + + assert pipeline.checkpoint_processes() == OrderedDict(task_2=second_processor) @staticmethod - def test_checkpoint_data(pipeline_type, processor_type): - """Checkpoint data should be stored correctly""" - proc_1 = FunctionProcessor(function=lambda x: x + 5, is_checkpoint=True) - proc_2 = processor_type(is_checkpoint=False) - pipeline = pipeline_type(task_1=proc_1, task_2=proc_2) + def test_checkpoint_data(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: + """Tests that the checkpoint data is stored correctly. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + first_processor = FunctionProcessor(processing_function=lambda x: x + 5, is_checkpoint=True) + second_processor = processor_type(is_checkpoint=False) + pipeline = pipeline_type(task_1=first_processor, task_2=second_processor) pipeline(0) - assert proc_1.checkpoint_data == 5 + + assert first_processor.checkpoint_data == 5 @staticmethod - def test_from_checkpoint(pipeline_type, processor_type): - """Resuming from a checkpoint should succeed""" - proc_1 = FunctionProcessor(function=lambda x: x + 5, is_checkpoint=True) - proc_2 = processor_type(is_checkpoint=False) - pipeline = pipeline_type(task_1=proc_1, task_2=proc_2) - proc_1.checkpoint_data = 1 - out = pipeline.from_checkpoint() - assert out == 2 + def test_from_checkpoint(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: + """Tests that resuming from a checkpoint succeeds. + + Args: + pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + first_processor = FunctionProcessor(processing_function=lambda x: x + 5, is_checkpoint=True) + second_processor = processor_type(is_checkpoint=False) + pipeline = pipeline_type(task_1=first_processor, task_2=second_processor) + first_processor.checkpoint_data = 1 + output = pipeline.from_checkpoint() + + assert output == 2 diff --git a/tests/unit_tests/corelay/pipeline/test_spectral.py b/tests/unit_tests/corelay/pipeline/test_spectral.py index e354dc6..d5def88 100644 --- a/tests/unit_tests/corelay/pipeline/test_spectral.py +++ b/tests/unit_tests/corelay/pipeline/test_spectral.py @@ -1,218 +1,271 @@ -"""Test spectral pipeline and processors. - -""" +"""A module that contains unit tests for the ``corelay.io.spectral`` module.""" import os +import numpy import pytest -import numpy as np -from numpy import pi - +from numpy.typing import NDArray -import matplotlib.pyplot as plt +from matplotlib import pyplot from corelay.pipeline.spectral import SpectralEmbedding, SpectralClustering from corelay.processor.affinity import SparseKNN -from corelay.processor.laplacian import SymmetricNormalLaplacian -from corelay.processor.embedding import EigenDecomposition from corelay.processor.clustering import KMeans +from corelay.processor.embedding import EigenDecomposition +from corelay.processor.laplacian import SymmetricNormalLaplacian -@pytest.fixture(scope='module') -def spiral_data(n=150): - """Sample some double spiral data points +@pytest.fixture(name='spiral_data', scope='module') +def get_spiral_data_fixture(number_of_samples_per_class: int = 150) -> NDArray[numpy.float64]: + """A fixture that produces data points that has the shape of a spiral. - Parameters - ---------- - n : int - samples per class of the two classes + Args: + number_of_samples_per_class (int, optional): Number of samples per class. Defaults to 150. + Returns: + NDArray[numpy.float64]: Returns a NumPy array of shape (300, 2) containing the spiral data. """ - np.random.seed(1345123) # fix seed for data - _ = np.random.uniform(size=(2, 100)).T # "part of the seed" - # generates double-spiral data in 2-d with N data points - theta = np.sqrt(np.random.rand(n)) * 2 * pi + # Fixes the seed for the random number generator to ensure reproducibility (100 random numbers are sampled uniformly as "part of the seed"; I do + # not know why the original author of this unit test did this, but I assume it is to remove some bad initial data points) + numpy.random.seed(1345123) + _ = numpy.random.uniform(size=(2, 100)).T - r_a = 2 * theta + pi - data_a = np.array([np.cos(theta) * r_a, np.sin(theta) * r_a]).T - x_a = data_a + np.random.randn(n, 2) * .5 + # Generates double-spiral data in 2D with N data points + theta = numpy.sqrt(numpy.random.rand(number_of_samples_per_class)) * 2 * numpy.pi - r_b = -2 * theta - pi - data_b = np.array([np.cos(theta) * r_b, np.sin(theta) * r_b]).T - x_b = data_b + np.random.randn(n, 2) * .5 + r_a = 2 * theta + numpy.pi + data_a = numpy.array([numpy.cos(theta) * r_a, numpy.sin(theta) * r_a]).T + x_a = data_a + numpy.random.randn(number_of_samples_per_class, 2) * 0.5 - return np.append(x_a, x_b, axis=0) + r_b = -2 * theta - numpy.pi + data_b = numpy.array([numpy.cos(theta) * r_b, numpy.sin(theta) * r_b]).T + x_b = data_b + numpy.random.randn(number_of_samples_per_class, 2) * 0.5 + return numpy.append(x_a, x_b, axis=0) -@pytest.fixture(scope='module') -def k_knn(spiral_data): - """Choose k for KNN""" - return int(np.log(spiral_data.shape[0])) +@pytest.fixture(name='number_of_neighbors', scope='module') +def get_number_of_neighbors_fixture(spiral_data: NDArray[numpy.float64]) -> int: + """A fixture that choose a suitable number of neighbors for the k-nearest neighbors algorithm. + Args: + spiral_data (NDArray[numpy.float64]): The spiral test data. + + Returns: + int: Returns a suitable number of neighbors for the k-nearest neighbors algorithm. + """ + + return int(numpy.log(spiral_data.shape[0])) + + +@pytest.fixture(name='number_of_eigenvalues', scope='module') +def get_number_of_eigenvalues_fixture() -> int: + """A fixtures that chooses a suitable number of eigenvalues. + + Returns: + int: Returns a suitable number of eigenvalues. + """ -@pytest.fixture(scope='module') -def k_eig(): - """Number for eigen values""" return 8 -@pytest.fixture(scope='module') -def k_clusters(): - """Number of clusters""" +@pytest.fixture(name='number_of_clusters', scope='module') +def get_number_of_clusters_fixture() -> int: + """A fixture that chooses a suitable number of clusters. + + Returns: + int: Returns a suitable number of clusters. + """ + return 4 -class TestSpectral: - """Test class for SpectralClustering""" +class TestSpectralEmbeddingAndSpectralClustering: + """Contains unit tests for the ``SpectralEmbedding`` and ``SpectralClustering`` classes.""" + @staticmethod - def test_spectral_embedding_instantiation(): - """test whether we can instantiate a spectral embedding instance successfully""" + def test_spectral_embedding_instantiation() -> None: + """Tests whether we can instantiate a spectral embedding instance successfully.""" + SpectralEmbedding() @staticmethod - def test_spectral_clustering_instantiation(): - """test whether we can instantiate a spectral clustering instance successfully""" + def test_spectral_clustering_instantiation() -> None: + """Tests whether we can instantiate a spectral clustering instance successfully.""" + SpectralClustering() @staticmethod - def test_data_generation(spiral_data): - """sanity check. make sure the data looks as expected (from the outside)""" - assert isinstance(spiral_data, np.ndarray), f'Expected numpy.ndarray type, got {type(spiral_data)}' - assert spiral_data.shape == (300, 2), f'Expected spiral_data shape (300, 2), got {spiral_data.shape}' + def test_data_generation(spiral_data: NDArray[numpy.float64]) -> None: + """Performs a sanity check to make sure the data looks as expected (from the outside). + + Args: + spiral_data (NDArray[numpy.float64]): The spiral test data. + """ + + assert isinstance(spiral_data, numpy.ndarray), f'Expected NumPy arrays, got {type(spiral_data)}.' + assert spiral_data.shape == (300, 2), f'Expected the spiral test data to be of shape (300, 2), got {spiral_data.shape}.' @staticmethod - def test_spectral_embedding_default_params(spiral_data): - """test whether the SE operates on data all the way through, using its default parameters.""" + def test_spectral_embedding_default_params(spiral_data: NDArray[numpy.float64]) -> None: + """Tests whether the ``SpectralEmbedding`` operates on data all the way through, using its default parameters. + + Args: + spiral_data (NDArray[numpy.float64]): The spiral test data. + """ + pipeline = SpectralEmbedding() output = pipeline(spiral_data) - assert isinstance(output, tuple), f'Expected tuple type output, got {type(output)}' - assert len(output) == 2, f'Expected output length of 2, got {len(output)}' - - eigval, eigvec = output - assert isinstance(eigval, np.ndarray), f'Expected eigval to be numpy.ndarray, but got {type(eigval)}' - assert isinstance(eigvec, np.ndarray), f'Expected eigvec to be numpy.ndarray, but got {type(eigvec)}' - assert eigvec.shape[0] == spiral_data.shape[0], ( - 'Expected number of eigenvectors to be identical to number of samples ' - f'({spiral_data.shape[0]}), but got {eigvec.shape[0]}' + assert isinstance(output, tuple), f'Expected the output to be of type tuple, got {type(output)}.' + assert len(output) == 2, f'Expected an output length of 2, got {len(output)}.' + + eigenvalues, eigenvectors = output + assert isinstance(eigenvalues, numpy.ndarray), f'Expected the eigenvalues to be NumPy arrays, but got {type(eigenvalues)}.' + assert isinstance(eigenvectors, numpy.ndarray), f'Expected the eigenvectors to be NumPy arrays, but got {type(eigenvectors)}.' + assert eigenvectors.shape[0] == spiral_data.shape[0], ( + f'Expected the number of eigenvectors to be identical to number of samples ({spiral_data.shape[0]}), but got {eigenvectors.shape[0]}' ) - assert eigvec.shape[1] == eigval.size, ( - f'Expected dim of eigenvectors {eigvec.shape[1]} be be identical ' - f'to the number of reported eigenvalues {eigval.size}' + assert eigenvectors.shape[1] == eigenvalues.size, ( + f'Expected the dimensionality of the eigenvectors {eigenvectors.shape[1]} to be identical to the number of reported eigenvalues ' + f'{eigenvalues.size}.' ) @staticmethod - def test_spectral_clustering_default_params(spiral_data): - """test whether the SC operates on data all the way through, using its default parameters.""" - pipeline = SpectralClustering() - output = pipeline(spiral_data) - assert isinstance(output, tuple), f'Expected tuple type output, got {type(output)}' - assert len(output) == 2, f'Expected output length of 2, got {len(output)}' + def test_spectral_clustering_default_params(spiral_data: NDArray[numpy.float64]) -> None: + """Tests whether the ``SpectralClustering`` operates on data all the way through, using its default parameters. - eigenstuff, labels = output - assert isinstance(eigenstuff, tuple), ( - f'Expected tuple type output for eigenstuff, got {type(eigenstuff)}' - ) - assert len(eigenstuff) == 2, ( - f'Expected eigenstuff length of 2, got {len(eigenstuff)}' - ) + Args: + spiral_data (NDArray[numpy.float64]): The spiral test data. + """ - eigval, eigvec = eigenstuff - assert isinstance(eigval, np.ndarray), f'Expected eigval to be numpy.ndarray, but got {type(eigval)}' - assert isinstance(eigvec, np.ndarray), f'Expected eigvec to be numpy.ndarray, but got {type(eigvec)}' - assert eigvec.shape[0] == spiral_data.shape[0], ( - 'Expected number of eigenvectors to be identical to number of samples ' - f'({spiral_data.shape[0]}), but got {eigvec.shape[0]}' + pipeline = SpectralClustering() + output = pipeline(spiral_data) + assert isinstance(output, tuple), f'Expected the output to be of type tuple, got {type(output)}.' + assert len(output) == 2, f'Expected an output length of 2, got {len(output)}.' + + eigenvalues_and_vectors, labels = output + assert isinstance(eigenvalues_and_vectors, tuple), f'Expected the output to be of type tuple, got {type(eigenvalues_and_vectors)}.' + assert len(eigenvalues_and_vectors) == 2, f'Expected the output tuple to be of length 2, got {len(eigenvalues_and_vectors)}.' + + eigenvalues, eigenvectors = eigenvalues_and_vectors + assert isinstance(eigenvalues, numpy.ndarray), f'Expected the eigenvalues to be NumPy arrays, but got {type(eigenvalues)}.' + assert isinstance(eigenvectors, numpy.ndarray), f'Expected the eigenvectors to be NumPy arrays, but got {type(eigenvectors)}.' + assert eigenvectors.shape[0] == spiral_data.shape[0], ( + f'Expected the number of eigenvectors to be identical to the number of samples ({spiral_data.shape[0]}), but got {eigenvectors.shape[0]}.' ) - assert eigvec.shape[1] == eigval.size, ( - f'Expected dim of eigenvectors {eigvec.shape[1]} be be identical to the number of ' - f'reported eigenvalues {eigval.size}' + assert eigenvectors.shape[1] == eigenvalues.size, ( + f'Expected the dimensionality of the eigenvectors {eigenvectors.shape[1]} be be identical to the number of reported eigenvalues ' + f'{eigenvalues.size}.' ) - assert isinstance(labels, np.ndarray), f'Expected labels to be numpy.ndarray, but got {type(labels)}' + assert isinstance(labels, numpy.ndarray), f'Expected labels to be NumPy arrays, but got {type(labels)}' assert labels.size == spiral_data.shape[0], ( - 'Expected number of labels to be identical to number of samples in spiral_data ' - f'({spiral_data.shape[0]}), but got {labels.size}' + 'Expected the number of labels to be identical to the number of samples in the spiral test data ({spiral_data.shape[0]}), but got ' + f'{labels.size}.' ) - assert labels.ndim == 1, f'Expected labels to be flat array, but was shaped {labels.shape}' + assert labels.ndim == 1, f'Expected the labels to be a flat array, but was shaped {labels.shape}.' @staticmethod - def test_spectral_clustering_step_by_step_custom_params(spiral_data, k_knn, k_eig, k_clusters): - """this test manually compares the pipelines working order against manually - executed steps with custom parameters attuned to the spiral data - - first, separately prepare processors for customized pipeline - customizations: - 1) parameters, as passed to the test function - 2) all processors (affected by changed parameters) produce outputs this time for later comparison - + def test_spectral_clustering_step_by_step_custom_params( + spiral_data: NDArray[numpy.float64], + number_of_neighbors: int, + number_of_eigenvalues: int, + number_of_clusters: int, + ) -> None: + """This test manually compares the pipelines working order against manually executed steps with custom parameters attuned to the spiral test + data. First, the processors are prepared separately in the same way they are set up in the custom pipeline. The parameters of the processors + are set to the values passed to the test function. All processors are then executed one by one, and the results are later compared to the + pipeline output. + + Args: + spiral_data (NDArray[numpy.float64]): The spiral test data. + number_of_neighbors (int): The number of neighbors for the k-nearest neighbors algorithm. + number_of_eigenvalues (int): The number of eigenvalues to compute. + number_of_clusters (int): The number of clusters for the k-means algorithm. """ - knn = SparseKNN(n_neighbors=k_knn, symmetric=True, is_output=True) - lap = SymmetricNormalLaplacian(is_output=True) + sparse_knn = SparseKNN(n_neighbors=number_of_neighbors, symmetric=True, is_output=True) + symmetric_normal_laplacian = SymmetricNormalLaplacian(is_output=True) - v0 = np.random.rand(spiral_data.shape[0]) # some fixed init vector - v0 /= np.linalg.norm(v0, 1) - eig = EigenDecomposition(n_eigval=k_eig, is_output=True, - kwargs={'v0': v0}) + # Generates a random fixed initialization vector for the eigenvalue decomposition + initialization_vector = numpy.random.rand(spiral_data.shape[0]) + initialization_vector /= numpy.linalg.norm(initialization_vector, 1) + eigen_decomposition = EigenDecomposition( + n_eigval=number_of_eigenvalues, + is_output=True, + kwargs={'v0': initialization_vector} + ) random_state = 0 - kmn = KMeans(n_clusters=k_clusters, is_output=True, - kwargs={'random_state': random_state}) + k_means = KMeans( + n_clusters=number_of_clusters, + is_output=True, + kwargs={'random_state': random_state} + ) pipeline = SpectralClustering( - affinity=knn, - laplacian=lap, - embedding=eig, - clustering=kmn + affinity=sparse_knn, + laplacian=symmetric_normal_laplacian, + embedding=eigen_decomposition, + clustering=k_means + ) + + pipeline_output = pipeline(spiral_data) + assert len(pipeline_output) == 4, ( + f'The length of the output was expected to be 4 (affinity, laplacian, eigenvalues, and labels), but is {len(pipeline_output)}.' ) - output_pipeline = pipeline(spiral_data) - assert len(output_pipeline) == 4, ( - f'length of output expected to be 4 (affinity, laplacian, embedding, labels), but is {len(output_pipeline)}' + # Unpacks the pipeline results + pipeline_affinity, pipeline_laplacian, pipeline_eigenvalues, pipeline_labels = pipeline_output + + # Produces the results manually and compares them to the pipeline output + manual_distance = pipeline.pairwise_distance(spiral_data) + manual_affinity = sparse_knn(manual_distance) + numpy.testing.assert_array_equal( + numpy.array(manual_affinity.todense()), + numpy.array(pipeline_affinity.todense()), + 'The affinity matrix produces manually differs from the one produced by the pipeline.' ) - # unpack pipeline results - aff_pipe, lap_pipe, eig_pipe, label_pipe = output_pipeline - - # produce results manually and compare to pipeline output - dist_man = pipeline.pairwise_distance(spiral_data) - aff_man = knn(dist_man) - np.testing.assert_array_equal(np.array(aff_man.todense()), - np.array(aff_pipe.todense()), - 'Affinity matrices are not equal!') - - lap_man = lap(aff_man) - np.testing.assert_array_equal(np.array(lap_man.todense()), - np.array(lap_pipe.todense()), - 'Laplacians are not equal!') - - eig_man = eig(lap_man) - np.testing.assert_array_equal(eig_man[0], - eig_pipe[0], - 'Eigenvalues not equal!') - np.testing.assert_array_equal(eig_man[1], - eig_pipe[1], - 'Eigenvectors not equal enough!') - - label_man = kmn(eig_man[1]) - np.testing.assert_array_equal(label_man, - label_pipe, - 'Label vectors not equal!') + manual_laplacian = symmetric_normal_laplacian(manual_affinity) + numpy.testing.assert_array_equal( + numpy.array(manual_laplacian.todense()), + numpy.array(pipeline_laplacian.todense()), + 'The laplacian produced manually differs from the one produced by the pipeline.' + ) + + manual_eigenvalues = eigen_decomposition(manual_laplacian) + numpy.testing.assert_array_equal( + manual_eigenvalues[0], + pipeline_eigenvalues[0], + 'The eigenvalues produced manually differ from the one produced by the pipeline.' + ) + numpy.testing.assert_array_equal( + manual_eigenvalues[1], + pipeline_eigenvalues[1], + 'The eigenvectors produced manually differ from the one produced by the pipeline.' + ) + + manual_labels = k_means(manual_eigenvalues[1]) + numpy.testing.assert_array_equal( + manual_labels, + pipeline_labels, + 'The labels produced manually differ from the one produced by the pipeline.' + ) path = '/tmp/spectral_eigenvalues.pdf' - plt.figure() - plt.plot(eig_pipe[0][::-1], '.') - plt.xticks([]) - plt.yticks([]) - plt.savefig(path) + pyplot.figure() + pyplot.plot(pipeline_eigenvalues[0][::-1], '.') + pyplot.xticks([]) + pyplot.yticks([]) + pyplot.savefig(path) os.remove(path) path = '/tmp/spectral_labelling.pdf' - plt.figure() - plt.scatter(x=spiral_data[:, 0], y=spiral_data[:, 1], c=label_pipe) - plt.xticks([]) - plt.yticks([]) - plt.savefig(path) + pyplot.figure() + pyplot.scatter(x=spiral_data[:, 0], y=spiral_data[:, 1], c=pipeline_labels) + pyplot.xticks([]) + pyplot.yticks([]) + pyplot.savefig(path) os.remove(path) diff --git a/tests/unit_tests/corelay/processor/__init__.py b/tests/unit_tests/corelay/processor/__init__.py index e69de29..bf70dea 100644 --- a/tests/unit_tests/corelay/processor/__init__.py +++ b/tests/unit_tests/corelay/processor/__init__.py @@ -0,0 +1 @@ +"""A sub-package that contains unit tests for the ``corelay.processor`` sub-package.""" diff --git a/tests/unit_tests/corelay/processor/test_base.py b/tests/unit_tests/corelay/processor/test_base.py index 0157161..d09c44c 100644 --- a/tests/unit_tests/corelay/processor/test_base.py +++ b/tests/unit_tests/corelay/processor/test_base.py @@ -1,32 +1,56 @@ -"""Test module for corelay/processor/base.py""" +"""A module that contains unit tests for the ``corelay.processor.base`` module.""" + +from collections.abc import Callable +from typing import Annotated, Any + import pytest from corelay.io import NoStorage -from corelay.processor.base import Processor, Param, FunctionProcessor, ensure_processor +from corelay.base import Param +from corelay.processor.base import Processor, FunctionProcessor, ensure_processor + +@pytest.fixture(name='processor_type', scope='module') +def get_processor_type_fixture() -> type[Processor]: + """A fixture that produces a custom ``Processor`` type. + + Returns: + type[Processor]: Returns a custom ``Processor`` type. + """ -@pytest.fixture(scope='module') -def processor_type(): - """Fixture Processor Subclass""" class MyProcessor(Processor): - """Custom Processor""" - param_1 = Param(str, mandatory=True) - param_2 = Param(int, -25, positional=True) - param_3 = Param((str, int), 'default') + """A custom ``Processor`` type.""" + + param_1: Annotated[str, Param(str, mandatory=True)] + param_2: Annotated[int, Param(int, -25, positional=True)] + param_3: Annotated[str | int, Param((str, int), 'default')] value = 42 text = 'apple' - def function(self, data): + def function(self, data: Any) -> Any: # pylint: disable=unused-argument + """An example function that implements the logic of the custom ``Processor``. + + Args: + data (Any): The input data, which is not used. + + Returns: + Any: Returns a fixed value of 21. + """ + return 21 return MyProcessor -@pytest.fixture(scope='module') -# pylint: disable=unused-argument -def kwargs(processor_type): - """Fixture dict for valid Processor Param values""" - kwargs = { +@pytest.fixture(name='kwargs', scope='module') +def get_kwargs_fixture() -> dict[str, Any]: + """A fixture that produces a dictionary with valid ``Param`` values for a ``Processor``. + + Returns: + dict[str, Any]: Returns a dictionary with valid ``Param`` values for a ``Processor``. + """ + + return { 'is_output': False, 'is_checkpoint': False, 'param_1': 'stuff', @@ -34,214 +58,409 @@ def kwargs(processor_type): 'param_3': 6, 'io': NoStorage() } - return kwargs -@pytest.fixture(scope='module') -def function(): - """Fixture test function""" - # pylint: disable=unused-argument - def some_function(self, data): +@pytest.fixture(name='function', scope='module') +def get_function_fixture() -> Callable[[Processor, int], int]: + """A fixture that produces a test function. + + Returns: + Callable[[Processor, int], int]: Returns a test function. + """ + + def some_function(self: Processor, data: int) -> int: # pylint: disable=unused-argument + """A test function that is supposed to be bound to a class and therefore has access to self. + + Args: + self (Processor): The instance of the class that the function is bound to. + data (int): The input data, which is not used. + + Returns: + int: Returns a fixed value of 42. + """ + return 42 + return some_function -@pytest.fixture(scope='module') -def unbound_function(): - """Fixture unbound function""" - # pylint: disable=unused-argument - def some_function(data): +@pytest.fixture(name='unbound_function', scope='module') +def get_unbound_function_fixture() -> Callable[[int], int]: + """A fixture that produces an unbound function, i.e., a function that is not bound to a class and does not have access to self. + + Returns: + Callable[[int], int]: Returns an unbound function. + """ + + def some_function(_data: int) -> int: + """A test function that is not supposed to be bound to a class and therefore has no access to self. + + Args: + _data (int): The input data, which is not used. + + Returns: + int: Returns a fixed value of 42. + """ + return 42 + return some_function class TestProcessor: - """Test class for Processor""" + """Contains unit tests for the ``Processor`` class.""" + @staticmethod - def test_params_tracked(processor_type): - """Processors should have Params tracked correctly""" + def test_params_tracked(processor_type: type[Processor]) -> None: + """Tests that processors track parameters correctly. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + assert ( ['is_output', 'is_checkpoint', 'io', 'param_1', 'param_2', 'param_3'] == list(processor_type.collect(Param)) ) @staticmethod - def test_creation(processor_type): - """Processors should instantiate properly in all cases""" + def test_creation(processor_type: type[Processor]) -> None: + """Tests that processors instantiate properly in all cases. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + processor_type() @staticmethod - def test_instance_assign(processor_type, kwargs): - """Parameter values passed as keyword arguments during instantiation should be properly set""" + def test_instance_assign(processor_type: type[Processor], kwargs: dict[str, Any]) -> None: + """Tests that the parameter values passed as keyword arguments during instantiation are properly set. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + kwargs (dict[str, Any]): A dictionary with valid ``Param`` values for a ``Processor``. + """ + processor = processor_type(**kwargs) - assert all(getattr(processor, key) == val for key, val in kwargs.items()) + assert all(getattr(processor, key) == value for key, value in kwargs.items()) @staticmethod - def test_instance_default(processor_type): - """Default values should be properly assigned""" - processor = processor_type(param_1="bacon") + def test_instance_default(processor_type: type[Processor]) -> None: + """Tests that the default values are be properly. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + processor = processor_type(param_1='bacon') + + assert hasattr(processor, 'param_2') assert processor.param_2 == -25 @staticmethod - def test_instance_positional(processor_type): - """Positional values should be properly assigned""" + def test_instance_positional(processor_type: type[Processor]) -> None: + """Tests that the positional values are properly assigned. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + processor = processor_type(44) + + assert hasattr(processor, 'param_2') assert processor.param_2 == 44 @staticmethod - def test_unknown_param(processor_type): - """Unknown parameters should raise an Exception""" + def test_unknown_param(processor_type: type[Processor]) -> None: + """Tests that unknown parameters raise an exception. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + with pytest.raises(TypeError): - processor_type(param_1="bacon", parma_0='monkey') + processor_type(param_1='bacon', parma_0='monkey') @staticmethod - def test_abstract_func(): - """Processor should be abstract and thus fail to instantiate""" + def test_abstract_func() -> None: + """Tests that the ``Processor`` class is abstract and thus fails to instantiate.""" + with pytest.raises(TypeError): - Processor() + Processor() # type: ignore[abstract] # pylint: disable=abstract-class-instantiated @staticmethod - def test_checkpoint(processor_type): - """Checkpoints should store data""" - processor = processor_type(param_1="bacon", is_checkpoint=True) - out = processor(0) - assert processor.checkpoint_data == out + def test_checkpoint(processor_type: type[Processor]) -> None: + """Tests that checkpoints properly store data. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + processor = processor_type(param_1='bacon', is_checkpoint=True) + output = processor(0) + + assert processor.checkpoint_data == output @staticmethod - def test_param_values(processor_type, kwargs): - """Params should be correctly set in __init__""" + def test_param_values(processor_type: type[Processor], kwargs: dict[str, Any]) -> None: + """Tests that the parameters are correctly set in the constructor. + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + kwargs (dict[str, Any]): A dictionary with valid ``Param`` values for a ``Processor``. + """ + processor = processor_type(**kwargs) assert processor.param_values() == kwargs @staticmethod - def test_copy_param_values(processor_type, kwargs): - """Copies should have identical Param values.""" + def test_copy_param_values(processor_type: type[Processor], kwargs: dict[str, Any]) -> None: + """Tests that copies of processors have identical Param values. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + kwargs (dict[str, Any]): A dictionary with valid ``Param`` values for a ``Processor``. + """ + processor = processor_type(**kwargs) - copy = processor.copy() - assert processor.param_values() == copy.param_values() + processor_copy = processor.copy() + + assert processor.param_values() == processor_copy.param_values() @staticmethod - def test_multiple_dtype(processor_type): - """A Parameter can be of multiple types.""" + def test_multiple_dtype(processor_type: type[Processor]) -> None: + """Tests that a ``Parameter`` can be of multiple types. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + processor_type(param_1='soup', param_3=21) processor_type(param_1='soup', param_3='spoon') @staticmethod - def test_mandatory_param(processor_type): - """Mandatory parameters should raise an Exception when accessed without being set""" + def test_mandatory_param(processor_type: type[Processor]) -> None: + """Tests that mandatory parameters raise an exception when accessed without being set. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + processor = processor_type() + with pytest.raises(TypeError): - # pylint: disable=pointless-statement - processor.param_1 + _ = processor.param_1 # type: ignore[attr-defined] @staticmethod - def test_wrong_type_param(processor_type): - """Passing a value with wrong type should raise a TypeError""" + def test_wrong_type_param(processor_type: type[Processor]) -> None: + """Tests that passing a value with wrong the type raises a ``TypeError``. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + with pytest.raises(TypeError): processor_type(param_1=2) @staticmethod - def test_bad_dtype(): - """Dtype should strictly only accept types.""" + def test_bad_dtype() -> None: + """Tests that the the ``dtype`` only accepts types, i.e., non-type values cannot be passed as argument for the ``dtype`` parameter.""" + with pytest.raises(TypeError): - # pylint: disable=unused-variable - class TestProcessor1(Processor): - """Test class with wrong Param""" - param = Param(2) + class TestProcessorWithWrongParam(Processor): + """A custom ``Processor`` with wrong a Param.""" + + param: Annotated[int, Param(2)] + """A wrong ``Param`` with a non-type value as dtype.""" + + def function(self, data: Any) -> Any: + """An example function that implements the logic of the custom ``Processor``. + + Args: + data (Any): The input data. + + Returns: + Any: Returns the input data. + """ + + return data + + _ = TestProcessorWithWrongParam() @staticmethod - def test_update_defaults(processor_type): - """Parameter instance default values can be updated""" - proc = processor_type(param_1='soup') - proc.update_defaults(param_2=1) - assert proc.param_2 == 1 + def test_update_defaults(processor_type: type[Processor]) -> None: + """Tests that the default values of a ``Parameter`` instance can be updated. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + processor = processor_type(param_1='soup') + processor.update_defaults(param_2=1) + + assert hasattr(processor, 'param_2') + assert processor.param_2 == 1 + assert processor.param_2 == 1 @staticmethod - def test_reset_defaults(processor_type): - """Parameter instance default values can be reset""" - proc = processor_type(param_1='soup') - proc.update_defaults(param_2=1) - proc.reset_defaults() - assert proc.param_2 == -25 + def test_reset_defaults(processor_type: type[Processor]) -> None: + """Tests that the default values of a ``Parameter`` instance can be reset. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + processor = processor_type(param_1='soup') + processor.update_defaults(param_2=1) + processor.reset_defaults() + + assert hasattr(processor, 'param_2') + assert processor.param_2 == -25 @staticmethod - def test_reset_defaults_assigned(processor_type): - """Resetting Param default values should go back to returning instantiation-time default values""" - proc = processor_type(param_1='soup', param_2=2) - proc.update_defaults(param_2=1) - proc.reset_defaults() - assert proc.param_2 == 2 + def test_reset_defaults_assigned(processor_type: type[Processor]) -> None: + """Tests that resetting the default values of ``Param`` instances go back to returning instantiation-time default values. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + processor = processor_type(param_1='soup', param_2=2) + processor.update_defaults(param_2=1) + processor.reset_defaults() + + assert hasattr(processor, 'param_2') + assert processor.param_2 == 2 @staticmethod - def test_update_defaults_wrong_dtype(processor_type): - """Updating Param default values with the wrong type should raise a TypeError""" - proc = processor_type(param_1='soup') + def test_update_defaults_wrong_dtype(processor_type: type[Processor]) -> None: + """Tests that updating the default values of ``Param`` instances with the wrong type raises a ``TypeError``. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + + processor = processor_type(param_1='soup') + with pytest.raises(TypeError): - proc.update_defaults(param_2='bogus') + processor.update_defaults(param_2='bogus') class TestFunctionProcessor: - """Test class for FunctionProcessor""" + """Contains unit tests for the ``FunctionProcessor`` class.""" + @staticmethod - def test_instantiation(unbound_function): - """instantiation should succeed with an unbound function as a keyword argument""" - FunctionProcessor(function=unbound_function) + def test_instantiation(unbound_function: Callable[[int], int]) -> None: + """Tests that the instantiation of a ``FunctionProcessor`` with an unbound function as a keyword argument should work. + + Args: + unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. + """ + + FunctionProcessor(processing_function=unbound_function) @staticmethod - def test_instance_call(unbound_function): - """Calling an instance should be the same result as calling the function""" - processor = FunctionProcessor(function=unbound_function) + def test_instance_call(unbound_function: Callable[[int], int]) -> None: + """Tests that calling a ``FunctionProcessor`` instance, that was constructed with a function that was passed as a keyword argument, results in + the same output as calling the function directly. + + Args: + unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. + """ + + processor = FunctionProcessor(processing_function=unbound_function) assert processor(0) == unbound_function(0) @staticmethod - def test_instance_call_positional(unbound_function): - """Calling an instance should be the same result as calling the function when given function positionally""" + def test_instance_call_positional(unbound_function: Callable[[int], int]) -> None: + """Tests that calling a ``FunctionProcessor`` instance, that was constructed with a function that was passed as a positional argument, results + in the same output as calling the function directly. + + Args: + unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. + """ + processor = FunctionProcessor(unbound_function) assert processor(0) == unbound_function(0) @staticmethod - def test_instance_call_bound(function): - """Calling a bound method should behave correctly""" - processor = FunctionProcessor(function=function, bind_method=True) + def test_instance_call_bound(function: Callable[[Processor, int], int]) -> None: + """Tests that calling a bound method should behave correctly. + + Args: + function (Callable[[Processor, int], int]): The function that is to be used in the test. + """ + + processor = FunctionProcessor(processing_function=function, bind_method=True) assert processor(0) == function(processor, 0) @staticmethod - def test_non_callable(): - """Passing a non-callable as a function should raise a TypeError""" + def test_non_callable() -> None: + """Tests that passing a non-callable object as an argument for the ``function`` parameter of the ``FunctionProcessor`` constructor raises a + ``TypeError``. + """ + with pytest.raises(TypeError): - FunctionProcessor(function='monkey') + FunctionProcessor(processing_function='monkey') class TestEnsureProcessor: - """Test class for ensure_processor""" + """Contains unit tests for the ``ensure_processor`` function.""" + @staticmethod - def test_processor(processor_type): - """Passing an existing Processor should not create a new one.""" + def test_processor(processor_type: type[Processor]) -> None: + """Tests that passing an existing ``Processor`` to the ``ensure_processor`` function returns the original ``Processor`` instead of creating a + new one. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + processor = processor_type(param_1='giraffe') - ensured = ensure_processor(processor) - assert processor is ensured + ensured_processor = ensure_processor(processor) + + assert processor is ensured_processor @staticmethod - def test_function(unbound_function): - """Passing a function should create a FunctionProcessor""" - ensured = ensure_processor(unbound_function) - assert isinstance(ensured, FunctionProcessor) + def test_function(unbound_function: Callable[[int], int]) -> None: + """Tests that passing a function to the ``ensure_processor`` function returns a new ``FunctionProcessor`` instance that wraps the function. + Args: + unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. + """ + + ensured_processor = ensure_processor(unbound_function) + assert isinstance(ensured_processor, FunctionProcessor) @staticmethod - def test_invalid(): - """Passing anything but a callable or Processor should raise a TypeError""" + def test_invalid() -> None: + """Tests that passing a non-callable object that is not of type ``Processor`` to the ``ensure_processor`` function raises a ''TypeError''.""" + with pytest.raises(TypeError): - ensure_processor('mummy') + ensure_processor('mummy') # type: ignore[arg-type] @staticmethod - def test_default_param_omitted(processor_type): - """Passing a Param value should set its default""" + def test_default_param_omitted(processor_type: type[Processor]) -> None: + """Tests that passing a ``Param`` value as a keyword argument to the ``ensure_processor`` function sets the value of the ``Param`` in the + returned ``Processor`` instance. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + processor = processor_type(param_1='giraffe') - ensured = ensure_processor(processor, is_output=True) - assert ensured.is_output + ensured_processor = ensure_processor(processor, is_output=True) + + assert ensured_processor.is_output @staticmethod - def test_default_param_assigned(processor_type): - """Passing a Param value should have lower priority than the explicitly set Param value""" + def test_default_param_assigned(processor_type: type[Processor]) -> None: + """Tests that passing a ``Param`` value as a keyword argument the the ``ensure_processor`` function has a lower priority than explicitly set + ``Param`` values and should not overwrite them. + + Args: + processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + """ + processor = processor_type(param_1='giraffe', is_output=False) - ensured = ensure_processor(processor, is_output=True) - assert not ensured.is_output + ensured_processor = ensure_processor(processor, is_output=True) + + assert not ensured_processor.is_output diff --git a/tests/unit_tests/corelay/processor/test_clustering.py b/tests/unit_tests/corelay/processor/test_clustering.py index 482818a..fd76dcb 100644 --- a/tests/unit_tests/corelay/processor/test_clustering.py +++ b/tests/unit_tests/corelay/processor/test_clustering.py @@ -1,99 +1,152 @@ -"""Test clustering processors. - -""" +"""A module that contains unit tests for the ``corelay.processor.clustering`` module.""" import os +from importlib import import_module +from typing import Any +import numpy import pytest -import numpy as np +from numpy.typing import NDArray from sklearn.datasets import make_blobs from sklearn.metrics.pairwise import euclidean_distances -from corelay.processor import clustering from corelay.base import Param +from corelay.processor import clustering + +EXTRA_PROCESSORS = [] +"""Contains a list of extra clustering processors that are to be tested. The list of extra clustering processors depends on the availability of the +respective libraries, which can optionally be installed by the user. If the library is not installed, the processor will not be available and can +therefore not be tested. +""" try: - import hdbscan # pylint: disable=unused-import; # noqa: F401 + import_module('hdbscan') EXTRA_PROCESSORS = [clustering.HDBSCAN] except ImportError: - EXTRA_PROCESSORS = [] + pass -@pytest.fixture -def data(): - """Return data with 1000 elements with 5 blobs. +@pytest.fixture(name='data', scope='module') +def get_data_fixture() -> NDArray[numpy.float64]: + """A fixture that produces tests data with 1000 elements that are split into 5 blobs. + Returns: + NDArray[numpy.float64]: Returns the data, which is a 2D array of shape (1000, 2), i.e., 1000 samples with 2 features. """ - data, _ = make_blobs(1000, centers=5, random_state=100) # pylint: disable=unbalanced-tuple-unpacking - return data + blobs = make_blobs(1000, centers=5, random_state=100) + blob: NDArray[numpy.float64] = blobs[0] + return blob -@pytest.fixture -def tiny_data(): - """Return data with 50 elements with 5 blobs. +@pytest.fixture(name='tiny_data', scope='module') +def get_tiny_data_fixture() -> NDArray[numpy.float64]: + """A fixtures that produces tiny test data with 50 elements that are split into 5 blobs. + + Returns: + NDArray[numpy.float64]: Returns the data, which is a 2D array of shape (50, 2), i.e., 50 samples with 2 features. """ - data, _ = make_blobs(50, centers=5, random_state=100) # pylint: disable=unbalanced-tuple-unpacking - return data + blobs = make_blobs(50, centers=5, random_state=100) + blob: NDArray[numpy.float64] = blobs[0] + return blob + + +@pytest.fixture(name='distances', scope='module') +def get_distances_fixture(data: NDArray[numpy.float64]) -> NDArray[numpy.float64]: + """A fixtures that computes the euclidean distances between each pair of data points. -@pytest.fixture -def distances(data): - """Return euclidean distances on data. + Args: + data (NDArray[numpy.float64]): The data to compute the distances for. + Returns: + NDArray[numpy.float64]: Returns a distance matrix, which, given input data of shape (, ), is a 2D array + of shape (, ), i.e., the distances between each pair of samples. """ - return euclidean_distances(data) + distance_matrix: NDArray[numpy.float64] = euclidean_distances(data) + return distance_matrix -@pytest.mark.parametrize('processor', [clustering.AgglomerativeClustering, clustering.KMeans]) -def test_clustering(processor, data): - """Test clustering processors by checking that 5 found clusters are of similar size. +@pytest.mark.parametrize('processor_type', [clustering.AgglomerativeClustering, clustering.KMeans]) +def test_clustering(processor_type: type[clustering.Clustering], data: NDArray[numpy.float64]) -> None: + """Tests the clustering processors by checking that the 5 found clusters are approximately of similar size. + + Args: + processor_type (type[clustering.Clustering]): The clustering ``Processor`` that is to be used in the test. + data (NDArray[numpy.float64]): The data that is to be clustered. """ - emb = processor(n_clusters=5)(data) - # since blobs are equal the cluster sizes should be approximately the same size - assert len(np.unique(emb)) == 5 - assert (np.unique(emb, return_counts=True)[1] / 1000).std() < 0.05 + processor = processor_type(n_clusters=5) + computed_clustering = processor(data) + + # Checks that the number of unique cluster labels is equal to the number of clusters + assert len(numpy.unique(computed_clustering)) == 5 -@pytest.mark.parametrize('processor', [clustering.DBSCAN] + EXTRA_PROCESSORS) -def test_embedding_on_distances(processor, distances): - """Test clustering processors by checking that 5 found clusters calculated on precomputed distances - are of similar size. + # Since the blobs are of equal size the cluster sizes should also be approximately the same size + assert (numpy.unique(computed_clustering, return_counts=True)[1] / 1000).std() < 0.05 + +@pytest.mark.parametrize('processor_type', EXTRA_PROCESSORS + [clustering.DBSCAN]) +def test_embedding_on_distances(processor_type: type[clustering.Clustering], distances: NDArray[numpy.float64]) -> None: + """Tests the clustering processors by checking that the 5 found clusters that were determined from pre-computed distances are approximately of + similar size. + + Args: + processor_type (type[clustering.Clustering]): The clustering ``Processor`` that is to be used in the test. + distances (NDArray[numpy.float64]): The pre-computed distances of the data points that are used to cluster the data. """ - params = {'eps': 0.9} if 'eps' in processor.collect(Param) else {} - emb = processor(metric='precomputed', **params)(distances) - assert len(np.unique(emb)) == 5 - assert (np.unique(emb, return_counts=True)[1] / 1000).std() < 0.13 + params: dict[str, Any] = {'eps': 0.9} if 'eps' in processor_type.collect(Param) else {} + processor = processor_type(metric='precomputed', **params) + computed_clustering = processor(distances) + + assert len(numpy.unique(computed_clustering)) == 5 + assert (numpy.unique(computed_clustering, return_counts=True)[1] / 1000).std() < 0.13 -def test_embedding_on_distances_agg_clustering(distances): - """Test that 5 found clusters of AgglomerativeClustering on precomputed distances are of similar size. +def test_embedding_on_distances_using_agglomerative_clustering(distances: NDArray[numpy.float64]) -> None: + """Tests that the 5 clusters found using the ``AgglomerativeClustering`` class that were determined from pre-computed distances are approximately + of similar size. + + Args: + distances (NDArray[numpy.float64]): The pre-computed distances of the data points that are used to cluster the data. """ - emb = clustering.AgglomerativeClustering(n_clusters=5, metric='precomputed', linkage='average')(distances) - assert (np.unique(emb, return_counts=True)[1] / 1000).std() < 0.05 + + computed_clustering = clustering.AgglomerativeClustering(n_clusters=5, metric='precomputed', linkage='average')(distances) + assert (numpy.unique(computed_clustering, return_counts=True)[1] / 1000).std() < 0.05 -def test_dendrogram_creation(tiny_data): - """Test dendrogram creation with a given path. +def test_dendrogram_creation_with_file_path(tiny_data: NDArray[numpy.float64]) -> None: + """Tests the creation of a dendrogram for the specified data, where the dendrogram image file is specified as a path string. + Args: + tiny_data (NDArray[numpy.float64]): The data to create the dendrogram for. """ + output_path = '/tmp/dendrogram.png' - data2 = clustering.Dendrogram(output_file=output_path)(tiny_data) - np.testing.assert_equal(tiny_data, data2) + dendrogram_processor = clustering.Dendrogram(output_file=output_path) + output_data = dendrogram_processor(tiny_data) + + numpy.testing.assert_equal(tiny_data, output_data) assert os.path.exists(output_path) + os.remove(output_path) -def test_dendrogram_creation_with_file_object(tiny_data): - """Test dendrogram creation with a given file object. +def test_dendrogram_creation_with_file_object(tiny_data: NDArray[numpy.float64]) -> None: + """Tests the creation of a dendrogram for the specified data, where the dendrogram image file is specified as a file object. + Args: + tiny_data (NDArray[numpy.float64]): The data to create the dendrogram for. """ + output_path = '/tmp/dendrogram.png' - with open(output_path, 'wb') as f: - data2 = clustering.Dendrogram(output_file=f)(tiny_data) - np.testing.assert_equal(tiny_data, data2) + with open(output_path, 'wb') as image_file: + dendrogram_processor = clustering.Dendrogram(output_file=image_file) + output_data = dendrogram_processor(tiny_data) + + numpy.testing.assert_equal(tiny_data, output_data) assert os.path.exists(output_path) + os.remove(output_path) diff --git a/tests/unit_tests/corelay/processor/test_embedding.py b/tests/unit_tests/corelay/processor/test_embedding.py index 8ef447e..2612ca5 100644 --- a/tests/unit_tests/corelay/processor/test_embedding.py +++ b/tests/unit_tests/corelay/processor/test_embedding.py @@ -1,62 +1,103 @@ -"""Test embedding processors. +"""A module that contains unit tests for the ``corelay.processor.embedding`` module.""" -""" +from importlib import import_module +import numpy import pytest +from numpy.typing import NDArray from sklearn.datasets import load_digits from sklearn.metrics.pairwise import euclidean_distances +from sklearn.utils import Bunch from corelay.processor import embedding +from corelay.processor.base import Processor + +EMBEDDING_PROCESSORS: list[type[embedding.Embedding]] = [embedding.TSNEEmbedding, embedding.LLEEmbedding, embedding.PCAEmbedding] +"""Contains a list of embedding processors that are to be tested.""" + + +EXTRA_PROCESSORS: list[type[embedding.Embedding]] = [] +"""Contains a list of extra embedding processors that are to be tested. The list of extra embedding processors depends on the availability of the +respective libraries, which can optionally be installed by the user. If the library is not installed, the processor will not be available and can +therefore not be tested. +""" -EMBEDDING_PROCESSORS = [embedding.TSNEEmbedding, embedding.LLEEmbedding, embedding.PCAEmbedding] try: - import umap # pylint: disable=unused-import; # noqa: F401 + import_module('umap') EXTRA_PROCESSORS = [embedding.UMAPEmbedding] except ImportError: - EXTRA_PROCESSORS = [] + pass -@pytest.fixture -def data(): - """Return data with images of 2 kind of digits with shape (360, 64). +@pytest.fixture(name='data', scope='module') +def get_data_fixture() -> NDArray[numpy.float64]: + """A test fixture, which loads a digit dataset that comprises images of two kinds of digits with a shape of (360, 64). + Returns: + NDArray[numpy.float64]: Returns the data that was loaded from the dataset. """ - digits = load_digits(n_class=2) # shape 360 x 64 - # pylint: disable=no-member - return digits.data + digits: Bunch = load_digits(n_class=2) + digits_data: NDArray[numpy.float64] = digits['data'] + return digits_data + + +@pytest.fixture(name='distances', scope='module') +def get_distances_fixture(data: NDArray[numpy.float64]) -> NDArray[numpy.float64]: + """A test fixture, which takes the digit dataset loaded in the ``data`` fixture and computes the euclidean distances on it. -@pytest.fixture -def distances(data): - """Return euclidean distances on data. + Args: + data (NDArray[numpy.float64]): The input data that is to be used for the distance computation. This data comes from the ``data`` fixture. + Returns: + NDArray[numpy.float64]: Returns the euclidean distances that were computed on the input data. """ - return euclidean_distances(data) + distance_matrix: NDArray[numpy.float64] = euclidean_distances(data) + return distance_matrix -@pytest.mark.parametrize('processor', EMBEDDING_PROCESSORS + EXTRA_PROCESSORS) -def test_embedding(processor, data): - """Test embedding processors on data and check the dimensions. +@pytest.mark.parametrize('processor_type', EMBEDDING_PROCESSORS + EXTRA_PROCESSORS) +def test_embedding(processor_type: type[Processor], data: NDArray[numpy.float64]) -> None: + """Tests the embedding processors on the specified data and checks the dimensions of the result. + + Args: + processor_type (type[Processor]): The type of embedding processor that is to be tested. + data (NDArray[numpy.float64]): The input data that is to be used for the embedding. This data comes from the ``data`` fixture. """ - emb = processor()(data) - assert emb.shape == (360, 2) + processor = processor_type() + output_embedding: NDArray[numpy.float64] = processor(data) + + assert output_embedding.shape == (360, 2) -def test_eigen_decomposition(distances): - """Test eigen decomposition and check the dimensions. +def test_eigen_decomposition(distances: NDArray[numpy.float64]) -> None: + """Tests the eigen-decomposition of the distances and check the dimensions. + + Args: + distances (NDArray[numpy.float64]): The input data that is to be used for the eigen-decomposition. This data comes from the ``distances`` + fixture. """ - eigval, eigvec = embedding.EigenDecomposition(n_eigval=32)(distances) - assert eigval.shape == (32, ) - assert eigvec.shape == (360, 32) + eigen_decomposition = embedding.EigenDecomposition(n_eigval=32) + eigenvalues, eigenvector = eigen_decomposition(distances) + + assert eigenvalues.shape == (32, ) + assert eigenvector.shape == (360, 32) -@pytest.mark.parametrize('processor', [embedding.TSNEEmbedding] + EXTRA_PROCESSORS) -def test_embedding_on_distances(processor, distances): - """Test embedding processors on precomputed distances and check the dimensions. +@pytest.mark.parametrize('processor_type', EXTRA_PROCESSORS + [embedding.TSNEEmbedding]) +def test_embedding_on_distances(processor_type: type[Processor], distances: NDArray[numpy.float64]) -> None: + """Tests the embedding processors on pre-computed distances and checks the dimensions. + + Args: + processor_type (type[Processor]): The type of embedding processor that is to be tested. + distances (NDArray[numpy.float64]): The input data that is to be used for the embedding. This data comes from the ``distances`` fixture. """ - emb = processor(metric='precomputed')(distances) - assert emb.shape == (360, 2) + + processor = processor_type(metric='precomputed') + output_embedding = processor(distances) + + assert output_embedding.shape == (360, 2) diff --git a/tests/unit_tests/corelay/processor/test_flow.py b/tests/unit_tests/corelay/processor/test_flow.py index fa3e5a3..88b63c4 100644 --- a/tests/unit_tests/corelay/processor/test_flow.py +++ b/tests/unit_tests/corelay/processor/test_flow.py @@ -1,81 +1,104 @@ -"""Test module for corelay/processor/flow.py""" +"""A module that contains unit tests for the ``corelay.processor.flow`` module.""" + import pytest -from corelay.processor.flow import Shaper, Parallel, Sequential from corelay.processor.base import FunctionProcessor +from corelay.processor.flow import Shaper, Parallel, Sequential class TestShaper: - """Test class for Shaper""" + """Contains unit tests for the ``Shaper`` class.""" + @staticmethod - def test_extract(): - """Extracting a single element should succeed""" + def test_extract() -> None: + """Tests that extracting a single element succeeds.""" + shaper = Shaper(indices=(1,)) assert shaper([1, 2, 3]) == (2,) @staticmethod - def test_extract_multi(): - """Extracting multiple elements should succeed""" + def test_extract_multi() -> None: + """Tests that extracting multiple elements succeeds.""" + shaper = Shaper(indices=(1, 2)) assert shaper([1, 2, 3]) == (2, 3) @staticmethod - def test_copy(): - """Specifying the same index multiple times should succeed""" + def test_copy() -> None: + """Tests that specifying the same index multiple times succeeds.""" + shaper = Shaper(indices=(1, 2, 1)) assert shaper([1, 2, 3]) == (2, 3, 2) @staticmethod - def test_stacked(): - """Using another inner tuple for specifying indices should succeed""" + def test_stacked() -> None: + """Tests that using another inner tuple for specifying indices succeeds.""" + shaper = Shaper(indices=(1, 2, (0, 2))) assert shaper([1, 2, 3]) == (2, 3, (1, 3)) @staticmethod - def test_stacked_too_much(): - """Specifying tuples any deeper than depth 2 should fail""" - with pytest.raises(TypeError): - Shaper(indices=(0, (1, (2,)))) + def test_stacked_multiple_levels() -> None: + """Tests that specifying indices that nest tuples deeper than depth 2 succeeds.""" + + shaper = Shaper(indices=(0, (1, (2,)))) + assert shaper([1, 2, 3]) == (1, (2, (3,))) class TestParallel: - """Test class for Parallel""" + """Contains unit tests for the ``Parallel`` class.""" + @staticmethod - def test_non_iterable(): - """Non-iterables should simply be copied as many times as there are children""" - parallel = Parallel(children=[FunctionProcessor(function=(lambda x, n=n: x + n)) for n in range(5)]) + def test_non_iterable() -> None: + """Tests that non-iterables are simply copied as many times as there are children.""" + + parallel = Parallel(children=[FunctionProcessor(processing_function=lambda x, n=n: x + n) for n in range(5)]) assert parallel(1) == (1, 2, 3, 4, 5) @staticmethod - def test_iterable(): - """Iterables are valid if they have the same length as there are children""" - parallel = Parallel(children=[FunctionProcessor(function=(lambda x, n=n: x + n)) for n in range(5)]) + def test_iterable() -> None: + """Tests that iterables are only valid if their length is the same as the number of children, when the children were given as a keyword + argument. + """ + + parallel = Parallel(children=[FunctionProcessor(processing_function=lambda x, n=n: x + n) for n in range(5)]) assert parallel((4, 3, 2, 1, 0)) == (4, 4, 4, 4, 4) @staticmethod - def test_iterable_positional(): - """Iterables are valid if they have the same length as there are children with children given positionally""" - parallel = Parallel([FunctionProcessor(function=(lambda x, n=n: x + n)) for n in range(5)]) + def test_iterable_positional() -> None: + """Tests that iterables are only valid if their length is the same as the number of children with children, when the children were given as + positional arguments. + """ + + parallel = Parallel([FunctionProcessor(processing_function=lambda x, n=n: x + n) for n in range(5)]) assert parallel((4, 3, 2, 1, 0)) == (4, 4, 4, 4, 4) @staticmethod - def test_iterable_length_mismatch(): - """Iterables are invalid if they have a different length compared to the number of children""" - parallel = Parallel(children=[FunctionProcessor(function=(lambda x, n=n: x + n)) for n in range(5)]) + def test_iterable_length_mismatch() -> None: + """Tests that iterables are invalid if their length is different from the number of children.""" + + parallel = Parallel(children=[FunctionProcessor(processing_function=lambda x, n=n: x + n) for n in range(5)]) with pytest.raises(TypeError): parallel((4, 3, 2, 1)) class TestSequential: - """Test class for Sequential""" + """Contains unit tests for the ``Sequential`` class.""" + @staticmethod - def test_sequential(): - """Input should be passed sequentially along all child processors""" - sequential = Sequential(children=[FunctionProcessor(function=lambda x, c=c: c + x) for c in 'bcde']) + def test_sequential() -> None: + """Tests that the input to the ``Sequential`` is passed sequentially through the processors as argument in the same order that the child + processors were specified, when the child processors were specified as a keyword argument. + """ + + sequential = Sequential(children=[FunctionProcessor(processing_function=lambda x, c=c: c + x) for c in 'bcde']) assert sequential('a') == 'edcba' @staticmethod - def test_sequential_positional(): - """Input should be passed sequentially along all child processors when given children positionally""" - sequential = Sequential([FunctionProcessor(function=lambda x, c=c: c + x) for c in 'bcde']) + def test_sequential_positional() -> None: + """Tests that the input to the ``Sequential`` is passed sequentially through the processors as argument in the same order that the child + processors were specified, when the child processors were specified as a positional argument. + """ + + sequential = Sequential([FunctionProcessor(processing_function=lambda x, c=c: c + x) for c in 'bcde']) assert sequential('a') == 'edcba' diff --git a/tests/unit_tests/corelay/processor/test_preprocessing.py b/tests/unit_tests/corelay/processor/test_preprocessing.py index 160133c..75a1a23 100644 --- a/tests/unit_tests/corelay/processor/test_preprocessing.py +++ b/tests/unit_tests/corelay/processor/test_preprocessing.py @@ -1,67 +1,133 @@ -"""Test module for corelay/processor/preprocessing.py""" -import numpy as np +"""A module that contains unit tests for the ``corelay.processor.preprocessing`` module.""" + +import numpy import pytest +from numpy.typing import NDArray from corelay.processor.preprocessing import Rescale, Resize, Pooling -@pytest.fixture -def no_channels(): - """Image-like array with no channel dimension.""" - return np.ones((10, 8, 8)) - - -@pytest.fixture -def channels_first(): - """Image-like array with dimensions: N, C, H, W""" - return np.ones((10, 3, 8, 8)) - - -@pytest.fixture -def channels_last(): - """Image-like array with dimensions: N, H, W, C""" - return np.ones((10, 8, 8, 3)) - - -@pytest.fixture -def random_noise(): - """Some normal distributed noise in an Image-like array.""" - return np.random.normal(0, 1, (10, 8, 8)) - - -@pytest.mark.parametrize('data,shape', [('random_noise', (10, 4, 4)), - ('channels_first', (10, 3, 4, 4)), - ('channels_last', (10, 4, 4, 3))]) -def test_rescaling(data, shape, request): - """Test for Rescaling PreProcessor""" - proc = Rescale(scale=0.5, channels_first=data == 'channels_first') - data = request.getfixturevalue(data) - out = proc(data) - assert out.shape == shape - assert data.max() >= out.max() - assert data.min() <= out.min() - - -@pytest.mark.parametrize('data,shape', [('random_noise', (10, 4, 16)), - ('channels_first', (10, 3, 4, 16)), - ('channels_last', (10, 4, 16, 3))]) -def test_resizing(data, shape, request): - """Test for Resizing PreProcessor""" - proc = Resize(width=16, height=4, channels_first=data == 'channels_first') - data = request.getfixturevalue(data) - out = proc(data) - assert out.shape == shape - assert data.max() >= out.max() - assert data.min() <= out.min() - - -@pytest.mark.parametrize('data,shape,stride', [('no_channels', (10, 4, 4), (1, 2, 2)), - ('channels_first', (10, 3, 4, 4), (1, 1, 2, 2)), - ('channels_last', (10, 4, 4, 3), (1, 2, 2, 1))]) -def test_pooling(data, shape, stride, request): - """Test for Pooling PreProcessor""" - proc = Pooling(stride=stride, pooling_function=np.sum) - data = request.getfixturevalue(data) - out = proc(data) - assert out.shape == shape - np.testing.assert_equal(out, 4 * np.ones(out.shape)) +@pytest.fixture(name='no_channels', scope='module') +def get_no_channels_fixture() -> NDArray[numpy.float64]: + """Generates a grayscale image-like array that contains all ones of shape `(number_of_samples, height, width)`. + + Returns: + NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, height, width)`. + """ + + return numpy.ones((10, 8, 8)) + + +@pytest.fixture(name='channels_first', scope='module') +def get_channels_first_fixture() -> NDArray[numpy.float64]: + """Generates an image-like array that contains all ones of shape `(number_of_samples, number_of_channels, height, width)`, where the channels come + before the image size. + + Returns: + NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, number_of_channels, height, width)`. + """ + + return numpy.ones((10, 3, 8, 8)) + + +@pytest.fixture(name='channels_last', scope='module') +def get_channels_last_fixture() -> NDArray[numpy.float64]: + """Generates an image-like array that contains all ones of shape `(number_of_samples, height, width, number_of_channels)`, where the channels come + last. + + Returns: + NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, height, width, number_of_channels)`. + """ + + return numpy.ones((10, 8, 8, 3)) + + +@pytest.fixture(name='random_noise', scope='module') +def get_random_noise_fixture() -> NDArray[numpy.float64]: + """Generates a grayscale image-like array that contains all normally distributed noise of shape `(number_of_samples, height, width)`. + + Returns: + NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, height, width)`. + """ + + return numpy.random.normal(0, 1, (10, 8, 8)) + + +@pytest.mark.parametrize( + 'fixture_name,shape', + [ + ('random_noise', (10, 4, 4)), + ('channels_first', (10, 3, 4, 4)), + ('channels_last', (10, 4, 4, 3)) + ] +) +def test_rescaling(fixture_name: str, shape: tuple[int, ...], request: pytest.FixtureRequest) -> None: + """Tests the rescaling pre-processing processor. + Args: + fixture_name (str): The name of the fixture that is to be used for the test. + shape (tuple[int, ...]): The expected shape of the output data. + request (pytest.FixtureRequest): The request object that is used to access the fixture. + """ + + processor = Rescale(scale=0.5, channels_first=fixture_name == 'channels_first') + input_data: NDArray[numpy.float64] = request.getfixturevalue(fixture_name) + + output_data: NDArray[numpy.float64] = processor(input_data) + + assert output_data.shape == shape + assert input_data.max() >= output_data.max() + assert input_data.min() <= output_data.min() + + +@pytest.mark.parametrize( + 'fixture_name,shape', + [ + ('random_noise', (10, 4, 16)), + ('channels_first', (10, 3, 4, 16)), + ('channels_last', (10, 4, 16, 3)) + ] +) +def test_resizing(fixture_name: str, shape: tuple[int, ...], request: pytest.FixtureRequest) -> None: + """Tests the resizing pre-processing processor. + + Args: + fixture_name (str): The name of the fixture that is to be used for the test. + shape (tuple[int, ...]): The expected shape of the output data. + request (pytest.FixtureRequest): The request object that is used to access the fixture. + """ + + processor = Resize(width=16, height=4, channels_first=fixture_name == 'channels_first') + input_data = request.getfixturevalue(fixture_name) + + output_data = processor(input_data) + + assert output_data.shape == shape + assert input_data.max() >= output_data.max() + assert input_data.min() <= output_data.min() + + +@pytest.mark.parametrize( + 'fixture_name,shape,stride', + [ + ('no_channels', (10, 4, 4), (1, 2, 2)), + ('channels_first', (10, 3, 4, 4), (1, 1, 2, 2)), + ('channels_last', (10, 4, 4, 3), (1, 2, 2, 1)) + ] +) +def test_pooling(fixture_name: str, shape: tuple[int, ...], stride: tuple[int, ...], request: pytest.FixtureRequest) -> None: + """Tests the pooling pre-processing processor. + + Args: + fixture_name (str): The name of the fixture that is to be used for the test. + shape (tuple[int, ...]): The expected shape of the output data. + stride (tuple[int, ...]): The stride of the pooling operation. + request (pytest.FixtureRequest): The request object that is used to access the fixture. + """ + + processor = Pooling(stride=stride, pooling_function=numpy.sum) + input_data = request.getfixturevalue(fixture_name) + + output_data = processor(input_data) + + assert output_data.shape == shape + numpy.testing.assert_equal(output_data, 4 * numpy.ones(output_data.shape)) diff --git a/tests/unit_tests/corelay/test_base.py b/tests/unit_tests/corelay/test_base.py index ab61af4..059b073 100644 --- a/tests/unit_tests/corelay/test_base.py +++ b/tests/unit_tests/corelay/test_base.py @@ -1,37 +1,45 @@ -"""Test module for corelay/base.py""" +"""A module that contains unit tests for the ``corelay.base`` module.""" + import pytest from corelay.base import Param class TestParam: - """Test class for Param""" + """Contains unit tests for the ``Param`` class.""" + @staticmethod - def test_instantiation(): - """Param should instantiate correctly when passing any dtype.""" + def test_instantiation() -> None: + """Tests that the ``Param`` class can be instantiated with any data type.""" + Param(object) @staticmethod - def test_dtype_not_assigned(): - """A TypeError should be raised when not providing any dtype.""" + def test_dtype_not_assigned() -> None: + """Tests that a ``TypeError`` is raised when no data type is provided when instantiating a ``Param`` instance.""" + with pytest.raises(TypeError): - # pylint: disable=no-value-for-parameter - Param() + Param() # type: ignore[call-arg] # pylint: disable=no-value-for-parameter @staticmethod - def test_dtype_no_type(): - """A TypeError should be raised when dtype is not a type.""" + def test_dtype_no_type() -> None: + """Tests that a ``TypeError`` is raised when specifying a data type when instantiating a ``Param`` instance that is not a type.""" + with pytest.raises(TypeError): - Param('monkey') + Param('monkey') # type: ignore[arg-type] @staticmethod - def test_dtype_multiple(): - """Param should support multiple dtypes in a tuple.""" + def test_dtype_multiple() -> None: + """Tests that the ``Param`` class can be instantiated with multiple data types in a tuple.""" + param = Param((object, type)) assert param.dtype == (object, type) @staticmethod - def test_dtype_single_to_tuple(): - """A single dtype should result in a class parameter of a tuple with a single type.""" + def test_dtype_single_to_tuple() -> None: + """Tests that, when a single data type was specified when instantiating an instance of ``Param``, that the ``dtype`` property returns a tuple + of types containing a single type. + """ + param = Param(object) assert param.dtype == (object,) diff --git a/tests/unit_tests/corelay/test_plugboard.py b/tests/unit_tests/corelay/test_plugboard.py index 4ec881b..4abe419 100644 --- a/tests/unit_tests/corelay/test_plugboard.py +++ b/tests/unit_tests/corelay/test_plugboard.py @@ -1,508 +1,722 @@ -"""Tests for corelay/plugboard.py""" +"""A module that contains unit tests for the ``corelay.plugboard`` module.""" + import pytest from corelay.plugboard import Slot, Plug, Plugboard class TestSlot: - """Test class for Slot""" + """Contains unit tests for the ``Slot`` class.""" + @staticmethod - def test_init(): - """Slot should successfully instantiate in any case""" + def test_init() -> None: + """Tests that a ``Slot`` can be instantiated successfully.""" + Slot() @staticmethod - def test_init_consistent_args(): - """If arguments are consistent, Slot should successfully instantiate""" + def test_init_consistent_args() -> None: + """Tests that a ``Slot`` can be instantiated successfully if its arguments are consistent.""" + Slot(dtype=int, default=5) @staticmethod - def test_init_inconsistent_args(): - """If arguments are inconsistent, Slot should raise TypeError when instantiating""" + def test_init_inconsistent_args() -> None: + """Tests that instantiating a ``Slot`` with inconsistent arguments raises an exception.""" + with pytest.raises(TypeError): Slot(dtype=str, default=5) @staticmethod - def test_init_unknown_args(): - """When unknown arguments are passed, Slot should raise TypeError when instantiating""" + def test_init_unknown_args() -> None: + """Tests that instantiating a ``Slot`` with unknown arguments raises an exception.""" + with pytest.raises(TypeError): Slot(monkey='banana') @staticmethod - def test_init_class_name(): - """When instantiated in a class, the __name__ parameter of Slot should be set accordingly""" + def test_init_class_name() -> None: + """Tests that when instantiating a class, the __name__ parameter of a contained ``Slot`` is set accordingly.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot() + """A test slot.""" + assert SlotHolder.my_slot.__name__ == 'my_slot' @staticmethod - def test_init_instance_default(): - """When instantiated in a class and accessed in an instance, with only default set, the default value should be - returned. - """ + def test_init_instance_default() -> None: + """Tests that when accessing a ``Slot`` in an instance, where only the default value is set, the default is returned.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(default=42) + """A test slot.""" + slot_holder = SlotHolder() assert slot_holder.my_slot == 42 @staticmethod - def test_instance_get(): - """Getting a value after setting it should succeed.""" + def test_instance_get() -> None: + """Tests that getting a value after setting it succeeds.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=int) + """A test slot.""" + slot_holder = SlotHolder() slot_holder.my_slot = 15 assert slot_holder.my_slot == 15 @staticmethod - def test_instance_get_no_default(): - """When instantiated in a class and accessed in an instance, without anything set, accessing the value should - raise a TypeError. - """ + def test_instance_get_no_default() -> None: + """Tests that when accessing a ``Slot`` in an instance, where nothing is set, an exception is raised.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot() + """A test slot.""" + slot_holder = SlotHolder() + with pytest.raises(TypeError): - # pylint: disable=pointless-statement - slot_holder.my_slot + _ = slot_holder.my_slot @staticmethod - def test_instance_set(): - """Setting a value without a default value afterwards should succeed.""" + def test_instance_set() -> None: + """Tests that everything works alright when not setting a default value, but then setting the value of the object before accessing it.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=int) + """A test slot.""" + slot_holder = SlotHolder() slot_holder.my_slot = 15 + assert slot_holder.my_slot == 15 + @staticmethod - def test_instance_set_wrong_dtype(): - """Setting a default value afterwards with a wrong dtype should not succeed.""" + def test_instance_set_wrong_dtype() -> None: + """Tests that setting a value with the wrong data type raises an exception.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(str) + """A test slot.""" + slot_holder = SlotHolder() + with pytest.raises(TypeError): slot_holder.my_slot = 15 @staticmethod - def test_instance_delete_unchanged(): - """Setting a value and deleting it afterwards with a set default value should return the default value""" + def test_instance_delete_unchanged() -> None: + """Tests that setting a value and deleting it afterwards with a set default value returns the default value""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(int, 42) + """A test slot.""" + slot_holder = SlotHolder() slot_holder.my_slot = 15 + del slot_holder.my_slot assert slot_holder.my_slot == 42 @staticmethod - def test_instance_delete_without_default(): - """Setting a value and deleting it afterwards with a set default value should raise an Exception.""" + def test_instance_delete_without_default() -> None: + """Tests that setting a value and deleting it afterwards without a default value raises an exception.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(int) + """A test slot.""" + slot_holder = SlotHolder() slot_holder.my_slot = 15 + with pytest.raises(TypeError): del slot_holder.my_slot @staticmethod - def test_class_set_dtype(): - """Setting a consistent dtype should succeed.""" + def test_class_set_dtype() -> None: + """Tests that setting a new data type that is consistent with the data type of the already existing default value succeeds.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=object, default=15) - SlotHolder.dtype = int + """A test slot.""" + + SlotHolder.my_slot.dtype = int @staticmethod - def test_class_set_dtype_inconsistent(): - """Setting an inconsistent dtype should fail.""" + def test_class_set_dtype_inconsistent() -> None: + """Tests that setting an new data type that is not consistent with the already existing default value fails.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=object, default=15) + """A test slot.""" + with pytest.raises(TypeError): SlotHolder.my_slot.dtype = str @staticmethod - def test_class_optional(): - """Slots with default values should be optional.""" + def test_class_optional() -> None: + """Tests that slots with default values are optional.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + assert SlotHolder.my_slot.optional @staticmethod - def test_class_not_optional(): - """Slots without default values should not be optional.""" + def test_class_not_optional() -> None: + """Tests that slots without default values are not optional.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=int) + """A test slot.""" + assert not SlotHolder.my_slot.optional @staticmethod - def test_class_call(): - """Calling a Slot should yield a Plug associated with the Slot""" + def test_class_call() -> None: + """Tests that calling a ``Slot`` yields the ``Plug`` associated with the ``Slot``.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + assert SlotHolder.my_slot is SlotHolder.my_slot().slot @staticmethod - def test_class_call_obj(): - """Calling a Slot with obj-argument should yield a Plug with that obj set""" + def test_class_call_obj() -> None: + """Tests that calling a ``Slot`` with an ``obj`` argument yields a ``Plug`` with the ``obj`` property set to that value.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=int) + """A test slot.""" + assert SlotHolder.my_slot(obj=15).obj == 15 @staticmethod - def test_class_call_default(): - """Calling a Slot with default-argument should yield a Plug with that default set""" + def test_class_call_default() -> None: + """Tests that calling a ``Slot`` with default-argument yields a ``Plug`` with that default set.""" + class SlotHolder: - """Holds a single Slot""" + """A test class that holds a single ``Slot``.""" + my_slot = Slot(dtype=int) + """A test slot.""" + assert SlotHolder.my_slot(default=15).default == 15 class TestPlug: - """Test class for Plug""" + """Contains unit tests for the ``Plug`` class.""" + @staticmethod - def test_init_with_slot_default(): - """instantiating a Plug with a slot's default value should succeed.""" + def test_init_with_slot_default() -> None: + """Tests that instantiating a ``Plug`` with the default value of a ``Slot`` succeeds.""" + slot = Slot(dtype=int, default=10) Plug(slot) @staticmethod - def test_init_no_slot_default(): - """instantiating a Plug without a slot's default value should fail.""" + def test_init_no_slot_default() -> None: + """Tests that instantiating a ``Plug`` without the default value of a ``Slot`` fails.""" + slot = Slot(dtype=int) + with pytest.raises(TypeError): Plug(slot) @staticmethod - def test_init_consistent(): - """instantiating a Plug with obj and default set as the correct slot's dtype should succeed.""" + def test_init_consistent() -> None: + """Tests that instantiating a ``Plug`` with the ``obj`` and ``default`` properties set to values that are consistent with the data type of the + ``Slot`` succeeds. + """ + slot = Slot(dtype=int) Plug(slot, obj=15, default=16) @staticmethod - def test_init_consistent_obj(): - """instantiating a Plug with obj set as the correct slot's dtype should succeed.""" + def test_init_consistent_obj() -> None: + """Tests that instantiating a ``Plug`` with the ``obj`` property set to a value that is consistent with the data type of the ``Slot`` + succeeds. + """ + slot = Slot(dtype=int) Plug(slot, obj=15) @staticmethod - def test_init_consistent_default(): - """instantiating a Plug with default set as the correct slot's dtype should succeed.""" + def test_init_consistent_default() -> None: + """Tests that instantiating a ``Plug`` with the ``default`` property set to a value that is consistent with the data type of the ``Slot`` + succeeds. + """ + slot = Slot(dtype=int) Plug(slot, default=15) @staticmethod - def test_init_inconsistent_obj(): - """instantiating a Plug with obj not set as slot's dtype should fail.""" + def test_init_inconsistent_obj() -> None: + """Tests that instantiating a ``Plug`` with the ``obj`` property set to a value that is not consistent with the data type of the ``Slot`` + fails. + """ + slot = Slot(dtype=str) + with pytest.raises(TypeError): Plug(slot, obj=15) @staticmethod - def test_init_inconsistent_default(): - """instantiating a Plug with obj not set as slot's dtype should fail.""" + def test_init_inconsistent_default() -> None: + """Tests that instantiating a ``Plug`` with the ``default`` property set to a value that is not consistent with the data type of the ``Slot`` + fails. + """ + slot = Slot(dtype=str) + with pytest.raises(TypeError): Plug(slot, default=15) @staticmethod - def test_obj_hierarchy_obj(): - """Accessing obj with slot default, plug default and obj set should return obj.""" + def test_obj_hierarchy_obj() -> None: + """Tests that accessing the ``obj`` property while the ``Slot`` and the ``Plug`` have a default values and the the ``obj`` property of the + ``Plug`` was initialized to a value, should return the value that ``obj`` initialized with. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot, obj='obj', default='default') + assert plug.obj == 'obj' @staticmethod - def test_obj_hierarchy_default(): - """Accessing obj with slot default and plug default set should return default.""" + def test_obj_hierarchy_default() -> None: + """Tests that accessing the ``obj`` property while the ``Slot`` and the ``Plug`` have a default values, should return the ``default`` value + of the ``Plug``. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') + assert plug.obj == 'default' @staticmethod - def test_obj_hierarchy_fallback(): - """Accessing obj with only slot default set should return slot default.""" + def test_obj_hierarchy_fallback() -> None: + """Tests that accessing the ``obj`` property while the ``Slot`` has a default value, should return the ``default`` value of the ``Slot``.""" + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) + assert plug.obj == 'fallback' @staticmethod - def test_default_hierarchy_obj(): - """Accessing default with slot default, default and obj set should return default.""" + def test_default_hierarchy_obj() -> None: + """Tests that accessing the ``default`` property while the ``Slot`` and the ``Plug`` have a default values and the the ``obj`` property of the + ``Plug`` was initialized to a value, should return the ``default`` value of the ``Plug``. + """ + slot = Slot(dtype=str, default='fallback') - plug = Plug(slot, default='default') + plug = Plug(slot, obj='obj', default='default') + assert plug.default == 'default' @staticmethod - def test_default_hierarchy_default(): - """Accessing default with slot default and plug default set should return default.""" + def test_default_hierarchy_default() -> None: + """Tests that accessing the ``default`` property while the ``Slot`` and the ``Plug`` have a default values, should return the ``default`` + value of the ``Plug``. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') + assert plug.default == 'default' @staticmethod - def test_default_hierarchy_fallback(): - """Accessing default with only slot default set should return slot default.""" + def test_default_hierarchy_fallback() -> None: + """Tests that accessing the ``default`` property while the ``Slot`` has a default value, should return the ``default`` value of the ``Slot``. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) + assert plug.default == 'fallback' @staticmethod - def test_fallback_hierarchy_obj(): - """Accessing fallback with slot default, default and obj set should return slot default.""" + def test_fallback_hierarchy_obj() -> None: + """Tests that accessing the ``fallback`` property while the ``Slot`` and the ``Plug`` have a default values and the the ``obj`` property of + the ``Plug`` was initialized to a value, should return the ``default`` value of the ``Slot``. + """ + slot = Slot(dtype=str, default='fallback') - plug = Plug(slot, default='default') + plug = Plug(slot, obj='obj', default='default') + assert plug.fallback == 'fallback' @staticmethod - def test_fallback_hierarchy_default(): - """Accessing fallback with slot default and plug default set should return slot default.""" + def test_fallback_hierarchy_default() -> None: + """Tests that accessing the ``fallback`` property while the ``Slot`` and the ``Plug`` have a default values, should return the ``default`` + value of the ``Slot``. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot, default='default') + assert plug.fallback == 'fallback' @staticmethod - def test_fallback_hierarchy_fallback(): - """Accessing fallback with only slot default set should return slot default.""" + def test_fallback_hierarchy_fallback() -> None: + """Tests that accessing the ``fallback`` property while the ``Slot`` has a default value, should return the ``default`` value of the ``Slot``. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) + assert plug.fallback == 'fallback' @staticmethod - def test_hierarchy_none(): - """Accessing any of default and fallback with only obj set should return None.""" + def test_hierarchy_none() -> None: + """Tests that accessing the ``default`` and ``fallback`` properties while the neither the ``Slot`` nor the ``Plug`` have a default value, but + the ``obj`` property of the ``Plug`` was initialized to a value returns `None`. + """ + slot = Slot(dtype=str) plug = Plug(slot, obj='obj') + assert plug.default is None assert plug.fallback is None @staticmethod - def test_delete_hierarchy(): - """Deleting obj with default set should return default.""" + def test_delete_hierarchy() -> None: + """Tests that deleting the value of the ``obj`` property with the ``Plug`` having a ``default`` set, returns the ``default`` value of the + ``Plug``. + """ + slot = Slot(dtype=str) plug = Plug(slot, default='default', obj='obj') del plug.obj + assert plug.obj == 'default' @staticmethod - def test_delete_hierarchy_last(): - """Deleting such that all obj, default and fallback are None should fail.""" + def test_delete_hierarchy_last() -> None: + """Tests that deleting the value of the ``default`` property of the ``Plug`` fails, when the ``Slot`` has no ``default`` value and the value + of the ``obj`` property of the ``Plug`` was previously deleted. + """ + slot = Slot(dtype=str) plug = Plug(slot, default='default', obj='obj') del plug.obj + with pytest.raises(TypeError): del plug.default @staticmethod - def test_obj_set(): - """Setting obj consistently should succeed.""" + def test_obj_set() -> None: + """Tests that setting the ``obj`` property of a ``Plug`` to a value that is consistent with the data type of the ``Slot`` succeeds.""" + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) plug.obj = 'obj' @staticmethod - def test_obj_set_inconsistent(): - """Setting obj inconsistently should fail.""" + def test_obj_set_inconsistent() -> None: + """Tests that setting the ``obj`` property of a ``Plug`` to a value that is not consistent with the data type of the ``Slot`` fails.""" + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) + with pytest.raises(TypeError): plug.obj = 15 @staticmethod - def test_obj_del(): - """Deleting obj with slot default should succeed.""" + def test_obj_del() -> None: + """Tests that deleting the value of the ``obj`` property of a ``Plug`` that is associated with a ``Slot`` that has a default value succeeds. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) plug.obj = 'obj' del plug.obj @staticmethod - def test_default_set(): - """Setting default consistently should succeed.""" + def test_default_set() -> None: + """Tests that setting the ``default`` property of a ``Plug`` to a value that is consistent with the data type of the ``Slot`` succeeds.""" + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) plug.default = 'default' @staticmethod - def test_default_set_inconsistent(): - """Setting default inconsistently should fail.""" + def test_default_set_inconsistent() -> None: + """Tests that setting the ``default`` property of a ``Plug`` to a value that is not consistent with the data type of the ``Slot`` fails.""" + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) + with pytest.raises(TypeError): plug.default = 15 @staticmethod - def test_default_del(): - """Deleting default with slot default should succeed.""" + def test_default_del() -> None: + """Tests that deleting the value of the ``default`` property of a ``Plug`` that is associated with a ``Slot`` that has a ``default`` value + succeeds. + """ + slot = Slot(dtype=str, default='fallback') plug = Plug(slot) plug.default = 'default' + del plug.default @staticmethod - def test_not_optional(): - """If neither default, nor slot default are set, object should not be optional.""" + def test_not_optional() -> None: + """Tests that if neither the ``Plug`` nor the ``Slot`` have a ``default`` value, the ``obj`` value is not optional.""" + slot = Slot(dtype=str) plug = Plug(slot, obj='obj') + assert not plug.optional @staticmethod - def test_optional(): - """If default is set, object should be optional.""" + def test_optional() -> None: + """Tests that if the ``default`` value of the ``Plug`` is set, the ``obj`` property is optional.""" + slot = Slot(dtype=str) plug = Plug(slot, default='default', obj='obj') + assert plug.optional @staticmethod - def test_slot_set_consistent(): - """Assigning a new slot with the correct dtype should succeed.""" + def test_slot_set_consistent() -> None: + """Tests that assigning a new ``Slot`` to a ``Plug``, that has the correct data type succeeds.""" + slot = Slot(dtype=object) plug = Plug(slot, obj='default') plug.slot = Slot(dtype=str) @staticmethod - def test_slot_set_inconsistent(): - """Assigning a new slot without the correct dtype should fail.""" + def test_slot_set_inconsistent() -> None: + """Tests that assigning a new ``Slot`` to a ``Plug``, that does not have the correct data type fails.""" + slot = Slot(dtype=object) plug = Plug(slot, obj='default') + with pytest.raises(TypeError): - plug.slot = slot(dtype=int) + plug.slot = Slot(dtype=int) @staticmethod - def test_slot_set_no_default(): - """Assigning a new slot without any other but the slot default value set should fail.""" + def test_slot_set_no_default() -> None: + """Tests that assigning a new ``Slot`` to a ``Plug``, that does not have a default value, and neither the original ``Slot`` nor the ``Plug`` + have a ``default`` or ``obj`` value fails. + """ + slot = Slot(dtype=object, default='fallback') plug = Plug(slot) + with pytest.raises(TypeError): - plug.slot = slot(dtype=str) + plug.slot = Slot(dtype=str) class TestPlugboard: - """Test class for Plugboard""" + """Contains unit tests for the ``Plugboard`` class.""" + @staticmethod - def test_init(): - """instantiating a Plugboard without anything set should succeed.""" + def test_init() -> None: + """Tests that instantiating a ``Plugboard`` without anything set succeeds.""" + Plugboard() @staticmethod - def test_init_unknown_kwargs(): - """instantiating a Plugboard with unknown kwargs should fail.""" + def test_init_unknown_kwargs() -> None: + """Tests that instantiating a ``Plugboard`` with unknown keyword arguments fails.""" + with pytest.raises(TypeError): Plugboard(stuff=19) @staticmethod - def test_init_args(): - """instantiating a Plugboard with any positional args should fail.""" + def test_init_args() -> None: + """Tests that instantiating a ``Plugboard`` with any positional arguments fails.""" + with pytest.raises(TypeError): - # pylint: disable=too-many-function-args - Plugboard(19) + Plugboard(19) # type: ignore[call-arg] # pylint: disable=too-many-function-args @staticmethod - def test_init_assign(): - """instantiating a Plugboard with kwargs identifying Slots should set those.""" + def test_init_assign() -> None: + """Tests that instantiating a ``Plugboard`` with keyword arguments identifying slots, sets the values of those slots.""" + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard(my_slot=19) + assert plugboard.my_slot == 19 @staticmethod - def test_default_get(): - """Accessing a Plugboard's Slot default values with an explicit obj value should succeed.""" + def test_default_get() -> None: + """Tests that accessing the default value of a ``Slot`` in a ``Plugboard`` that has an explicit ``obj`` value succeeds.""" + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard(my_slot=19) + assert plugboard.default.my_slot == 15 @staticmethod - def test_default_set(): - """Setting a Plugboard's Slot default values should succeed.""" + def test_default_set() -> None: + """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` that is consistent with the data type of the ``Slot`` succeeds.""" + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard() plugboard.default.my_slot = 17 + assert plugboard.my_slot == 17 @staticmethod - def test_default_set_dict(): - """Setting a Plugboard's Slot default values using a dict should succeed.""" + def test_default_set_dict() -> None: + """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` that is consistent with the data type of the ``Slot`` using a + dictionary succeeds. + """ + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard() - plugboard.default = dict(my_slot=17) + plugboard.default = {'my_slot': 17} + assert plugboard.my_slot == 17 @staticmethod - def test_default_set_dict_wrong(): - """Setting a Plugboard's Slot default values using anything but a dict should fail.""" + def test_default_set_dict_wrong() -> None: + """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` using anything but a dictionary or the attribute accessor of the + ``Slot`` fails. + """ + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard() + with pytest.raises(TypeError): - plugboard.default = 15 + plugboard.default = 15 # type: ignore[assignment] @staticmethod - def test_default_del(): - """Deleting a plugboard's slot default values should succeed.""" + def test_default_del() -> None: + """Tests that deleting the default value of a ``Slot`` in a ``Plugboard`` succeeds and reverts back to the default value of the ``Slot``.""" + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard() plugboard.default.my_slot = 17 del plugboard.default.my_slot + assert plugboard.my_slot == 15 @staticmethod - def test_default_set_influence_obj(): - """Setting a Plugboard's Slot default values should not influence the Plug obj value.""" + def test_default_set_influence_obj() -> None: + """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` does not influence the value of the ``Slot``.""" + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard(my_slot=19) plugboard.default.my_slot = 17 + assert plugboard.my_slot == 19 @staticmethod - def test_default_dir(): - """The default __dir__ should contain all Slots""" + def test_default_dir() -> None: + """Tests that the ``default`` property of the plugboard is a dictionary, that contains entries for all of its slots.""" + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard() + assert set(plugboard.collect(Slot)) == set(dir(plugboard.default)) @staticmethod - def test_update_defaults(): - """Setting a Plugboard's Slot default values using update_defaults should succeed.""" + def test_update_defaults() -> None: + """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` that is consistent with the data type of the ``Slot`` using the + ``update_defaults`` method succeeds. + """ + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard() plugboard.update_defaults(my_slot=17) + assert plugboard.my_slot == 17 @staticmethod - def test_reset_defaults(): - """Resetting a plugboard's slot default values should succeed.""" + def test_reset_defaults() -> None: + """Tests that resetting the default value of a ``Slot`` in a ``Plugboard`` succeeds.""" + class MyPlugboard(Plugboard): - """Custom Plugboard""" + """A test plugboard that holds a single ``Slot``.""" + my_slot = Slot(dtype=int, default=15) + """A test slot.""" + plugboard = MyPlugboard() plugboard.default.my_slot = 17 plugboard.reset_defaults() + assert plugboard.my_slot == 15 diff --git a/tests/unit_tests/corelay/test_tracker.py b/tests/unit_tests/corelay/test_tracker.py index 0b73924..f945016 100644 --- a/tests/unit_tests/corelay/test_tracker.py +++ b/tests/unit_tests/corelay/test_tracker.py @@ -1,79 +1,120 @@ -"""Test tracker functionalities. - -""" +"""A module that contains unit tests for the ``corelay.tracker`` module.""" import pytest from corelay.tracker import Tracker -@pytest.fixture(scope='module') -def tracked(): - """Tracked class fixture""" +@pytest.fixture(name='tracker_type', scope='module') +def get_tracked_fixture() -> type[Tracker]: + """Creates a sub-class of ``Tracker`` with some attributes. + + Returns: + type[Tracker]: A sub-class of ``Tracker`` with some attributes. + """ + class SubTracked(Tracker): - """Tracker Subclass""" + """A sub-class of ``Tracker`` with some attributes.""" + attr_1 = 42 attr_2 = 'apple' attr_3 = object attr_4 = 15 attr_5 = 'pear' attr_6 = str + return SubTracked -@pytest.fixture(scope='module') -def values(): - """Fixture of list of values, how they were written in the tracked fixture class.""" - result = dict( - attr_1=42, - attr_2='apple', - attr_3=object, - attr_4=15, - attr_5='pear', - attr_6=str - ) +@pytest.fixture(name='values', scope='module') +def get_values_fixture() -> dict[str, int | str | type]: + """Generates a list of values, that can be used to test the collection of values. + + Returns: + dict[str, int | str | type]: Returns a dictionary with values that can be used to test the collection of values. + """ + + result: dict[str, int | str | type] = { + 'attr_1': 42, + 'attr_2': 'apple', + 'attr_3': object, + 'attr_4': 15, + 'attr_5': 'pear', + 'attr_6': str + } return result -@pytest.fixture(scope='module') -# pylint: disable=unused-argument -def attributes(tracked): - """Fixture for some new values for Tracked Parameters""" - result = dict( - attr_1='value_1', - attr_2='value_2', - attr_3='value_3', - attr_4='value_4', - attr_5='value_5', - attr_6='value_6', - ) +@pytest.fixture(name='attributes', scope='module') +def get_attributes_fixture() -> dict[str, str]: + """Generates some new values for tracked parameters. + + Returns: + dict[str, str]: Returns a dictionary with new values for tracked parameters. + """ + + result: dict[str, str] = { + 'attr_1': 'value_1', + 'attr_2': 'value_2', + 'attr_3': 'value_3', + 'attr_4': 'value_4', + 'attr_5': 'value_5', + 'attr_6': 'value_6' + } return result -@pytest.fixture(scope='module') -def instance(tracked, attributes): - """Fixture instance with new attribute values""" - result = tracked() +@pytest.fixture(name='instance', scope='module') +def get_instance_fixture(tracker_type: type[Tracker], attributes: dict[str, str]) -> Tracker: + """Generates an instance of a tracker and adds new attributes to it to track. + + Args: + tracker_type (type[Tracker]): The type of the tracker to be created. + attributes (dict[str, str]): The attributes to be added to the tracker instance. + + Returns: + Tracker: Returns an instance of the tracker with the new attributes. + """ + + result = tracker_type() for key, value in attributes.items(): setattr(result, key, value) return result class TestTracker: - """Test class for Tracker""" + """Contains unit tests for the ``Tracker`` class.""" + @staticmethod - def test_collect(tracked): - """Types should be collected correctly and in order.""" - assert tracked.collect(int) == dict(attr_1=42, attr_4=15) - assert tracked.collect(str) == dict(attr_2='apple', attr_5='pear') - assert tracked.collect(type) == dict(attr_3=object, attr_6=str) + def test_collect(tracker_type: type[Tracker]) -> None: + """Tests that parameters of different types are collected correctly. + + Args: + tracker_type (type[Tracker]): The type of the tracker to be used for collecting the parameter values. + """ + + assert tracker_type.collect(int) == {'attr_1': 42, 'attr_4': 15} + assert tracker_type.collect(str) == {'attr_2': 'apple', 'attr_5': 'pear'} + assert tracker_type.collect(type) == {'attr_3': object, 'attr_6': str} @staticmethod - def test_collect_multiple(tracked, values): - """Collecting multiple dtypes should succeed""" - assert tracked.collect((int, str, type)) == values + def test_collect_multiple(tracker_type: type[Tracker], values: dict[str, int | str | type]) -> None: + """Tests that collecting parameters of multiple data types succeeds. + + Args: + tracker_type (type[Tracker]): The type of the tracker to be used for collecting the parameter values. + values (dict[str, int | str | type]): The expected values to be collected. + """ + + assert tracker_type.collect((int, str, type)) == values @staticmethod - def test_collect_attr(instance, attributes): - """Collecting instance attribute values by owner attributes should succeed.""" + def test_collect_attr(instance: Tracker, attributes: dict[str, str]) -> None: + """Tests that collecting instance attribute values of owner attributes succeeds. + + Args: + instance (Tracker): The instance of the tracker to be used for collecting the parameter values. + attributes (dict[str, str]): The expected values to be collected. + """ + assert instance.collect_attr((int, str, type)) == attributes diff --git a/tests/unit_tests/corelay/test_utils.py b/tests/unit_tests/corelay/test_utils.py index d0a6fdf..9e68b77 100644 --- a/tests/unit_tests/corelay/test_utils.py +++ b/tests/unit_tests/corelay/test_utils.py @@ -1,14 +1,13 @@ -"""Test corelay.utils - -""" +"""A module that contains unit tests for the ``corelay.utils`` module.""" import pytest -from corelay.utils import import_or_stub, Iterable, zip_equal +from corelay.utils import import_or_stub, zip_equal + +def test_conditional_import() -> None: + """Tests that the conditional import only fails for not installed packages when the modules are first used.""" -def test_conditional_import(): - """Test conditional import which fails only when actually using the imported module.""" non_existing_module = import_or_stub('non_existing_module') non_existing_function = import_or_stub('non_existing_module', 'non_existing_function') re = import_or_stub('re') @@ -22,15 +21,23 @@ def test_conditional_import(): findall('aba', 'a') -def test_conditional_import_of_multiple_functions(): - """Test conditional importing of multiple function from the same module.""" +def test_conditional_import_of_multiple_functions() -> None: + """Tests that the conditional importing can be used to import multiple functions from the same module.""" + match, fullmatch = import_or_stub('non_existing_module', ('match', 'fullmatch')) + + assert callable(match) + assert callable(fullmatch) + with pytest.raises(RuntimeError): match('aba', 'a') with pytest.raises(RuntimeError): fullmatch('aba', 'a') match, fullmatch = import_or_stub('re', ('match', 'fullmatch')) + + assert callable(match) + assert callable(fullmatch) match('aba', 'a') fullmatch('aba', 'a') @@ -38,48 +45,24 @@ def test_conditional_import_of_multiple_functions(): _, _ = import_or_stub('re', ('findall', 'non_existing')) -class TestIterable: - """Test class for Iterable""" - @staticmethod - def test_instance_check_all_member_type(): - """Iterable without member type should be any Iterable""" - assert isinstance([1, 'a', 3.5], Iterable) - - @staticmethod - def test_instance_check_single_member_type_positive(): - """Iterable with single member type should succeed""" - assert isinstance([1, 2, 3], Iterable[int]) - - @staticmethod - def test_instance_check_single_member_type_negative(): - """Iterable with single member type and wrong input should fail""" - assert not isinstance([1, 2, 'a'], Iterable[int]) - - @staticmethod - def test_instance_check_multiple_member_type_positive(): - """Iterable with multiple member types should succeed""" - assert isinstance([1, 2, 0.5], Iterable[int, float]) +class TestZipEqual: + """Contains unit tests for the ``zip_equal`` function.""" @staticmethod - def test_instance_check_multiple_member_type_negative(): - """Iterable with multiple member types and wrong input should fail""" - assert not isinstance([1, 'a', 0.5], Iterable[int, float]) - + def test_equal_length() -> None: + """Tests that zipping two iterables of equal length succeeds.""" -class TestZipEqual: - """Test class for zip_equal""" - @staticmethod - def test_equal_length(): - """Zipping 2 iterables of equal length should succeed""" assert tuple(zip_equal(range(3), 'abc')) == ((0, 'a'), (1, 'b'), (2, 'c')) @staticmethod - def test_many_equal_length(): - """Zipping more than 2 iterables of equal length should succeed""" + def test_many_equal_length() -> None: + """Tests that zipping more than two iterables of equal length succeeds.""" + assert tuple(zip_equal(*(range(3),) * 5)) == ((0,) * 5, (1,) * 5, (2,) * 5) @staticmethod - def test_unequal_length(): - """Zipping 2 iterables of unequal length should fail""" + def test_unequal_length() -> None: + """Tests that zipping two iterables of unequal length fails.""" + with pytest.raises(TypeError): tuple(zip_equal(range(3), 'abcd')) From 51874e273a96546dd41d2d38b85233cf40ee15ee Mon Sep 17 00:00:00 2001 From: David Neumann Date: Mon, 12 May 2025 15:53:20 +0200 Subject: [PATCH 11/19] Updated the Documentation Updated the documentation to reflect the changes made to CoRelAy. First of all, the Sphinx configuration was updated: - The "sphinxcontrib.datatemplates" extension was removed, because it was not used in the documentation. - Added the "sphinx.ext.intersphinx" extension to add links in the documentation to external documentations, e.g., the Python standard library documentation and the NumPy documentation. The documentation was largely extended and a bit reorganized. The API reference documentation was moved from the "reference" directory to the "api-reference" directory. This was done to better reflect the purpose of the documentation, since there are also bibliographical references in the documentation. The "Getting Started" section was expanded to new section with multiple sub-sections: "Installation", "Basic Usage", and an "Example Project". The installation and basic usage sections were moved from the original "Getting Started" section. The "Example Project" section contains a more elaborate example of how to use CoRelAy to analyze a dataset generated by Zennit using the SpRAy workflow that can be visualized using ViRelAy. A new "Contributor's Guide" section was added with sub-sections on how to report issues and feature requests, and how to contribute code or documentation. Finally, a new "Migration Guide" section was added to help users migrate from CoRelAy v0.2 to CoRelAy v0.3. This was done, because the changes made in CoRelAy v0.3 are quite extensive and it is not easy to find out what has changed and how to adapt existing code to the new version. Finally, more citations were added to the documentation to relevant literature. A custom CSS file was added to the documentation to change the alignment of the documentation text to be justified and to automatically break long words. This was done to improve the readability of the documentation and to align it with usual scientific documents. Also, the width of the labels in the bibliography was set to a fixed size, so that the labels all have the same width. This was done, because they looked inconsistent and messy before. The docstrings in the CoRelAy source code were updated to now use the ":py:*:" directives to link references to classes, functions, methods, modules, and values to their respective external documentation. The usage of type hints had to be changed in some places to accommodate the way that Sphinx AutoDoc and Intersphinx reference types. Also, some of the docstrings were updated to fix typos, improve clarity, or to add missing information. By default, Sphinx AutoDoc only documents the "__init__" method and leaves out all other special "dunder" methods, such as "__repr__". A list of the special methods that are used in the CoRelAy source code was added to the list of methods covered by Sphinx AutoDoc, so that these methods are also documented. Usage of the "Literal" type hint was totally removed from the source code, because it is not properly supported by Sphinx AutoDoc and made more problems than it solved. The code for retrieving the source code location of type aliases to "Literal" type hints was removed from the documentation configuration Python file, as it is no longer needed. The invocation of Sphinx in tox to build the documentation was updated to include the "--fresh-env" option, so that the documentation is always built in a fresh environment. This solves some issues with the documentation build, where the documentation was not updated correctly after changes were made to the source code or the documentation itself. Besides the updates to the documentation, the read me of the repository was also updated. The introduction was extended to better explain the CoRelAy library. A section on the features of CoRelAy was added. The installation instructions and the usage section were now wrapped in a new "Getting Started" section. The example code in the usage section was updated to reflect the changes made to the example scripts. The "Contributing" section was also updated to include a proper description and links to the contributors guide in the documentation. Also, the example scripts were updated to reflect the coding style and docstring conventions used in the CoRelAy library. They were moved from the "example" directory to the "docs/examples" directory. This was done because the examples are part part of the documentation and to keep the root directory clean and free of unnecessary clutter. Some of the "__repr__" methods in the CoRelAy source code had to be updated, because Sphinx AutoDoc uses the "__repr__" method to generate type information for parameters, which was interfering with the usage of "Annotated" type hints: since the instance of "Param" or "Task" is a parameter of the "Annotated" type hint, AutoDoc would call the "__repr__" method on them and interpret the output as the type. This is obviously weird and not what we want, so the "__repr__" methods were updated to return a string that is more suitable for the documentation. Also, the code for retrieving the source code of a lambda expression, which was used in the "__repr__" methods was massively updated to fix some edge cases that were not handled correctly before. --- .gitignore | 4 +- CHANGELOG.md | 16 + README.md | 97 +- docs/examples/corelay_basics.py | 127 ++ {example => docs/examples}/hdf5_structure.py | 6 +- .../examples}/memoize_spectral_pipeline.py | 46 +- .../examples}/virelay_analysis.py | 115 +- docs/source/_static/custom.css | 16 + docs/source/_templates/autosummary/module.rst | 50 +- docs/source/api-reference/index.rst | 19 + docs/source/bibliography.bib | 67 +- docs/source/conf.py | 184 +-- .../contributing-code-or-documentation.rst | 176 ++ docs/source/contributors-guide/index.rst | 21 + .../reporting-issues-and-feature-requests.rst | 15 + docs/source/getting-started.rst | 312 ---- docs/source/getting-started/basic-usage.rst | 178 ++ .../getting-started/example-project.rst | 326 ++++ docs/source/getting-started/index.rst | 12 + docs/source/getting-started/installation.rst | 32 + docs/source/images/spray-workflow.png | Bin 0 -> 554758 bytes docs/source/index.rst | 56 +- docs/source/migration-guide/index.rst | 10 + .../migrating-from-v0.2-to-v0.3.rst | 78 + docs/source/reference/index.rst | 17 - example/corelay_basics.py | 118 -- source/corelay/__init__.py | 7 +- source/corelay/base.py | 62 +- source/corelay/io/__init__.py | 5 +- source/corelay/io/hashing.py | 76 +- source/corelay/io/storage.py | 443 ++--- source/corelay/pipeline/__init__.py | 10 +- source/corelay/pipeline/base.py | 271 +-- source/corelay/pipeline/spectral.py | 77 +- source/corelay/plugboard.py | 390 +++-- source/corelay/processor/__init__.py | 5 +- source/corelay/processor/affinity.py | 70 +- source/corelay/processor/base.py | 229 +-- source/corelay/processor/clustering.py | 434 +++-- source/corelay/processor/distance.py | 169 +- source/corelay/processor/embedding.py | 325 ++-- source/corelay/processor/flow.py | 131 +- source/corelay/processor/laplacian.py | 67 +- source/corelay/processor/preprocessing.py | 186 ++- source/corelay/tracker.py | 138 +- source/corelay/utils.py | 210 ++- source/pyproject.toml | 8 +- source/tox.ini | 9 +- source/uv.lock | 1472 ++++++++--------- tests/linters/cspell/.cspell.json | 35 + tests/linters/cspell/package.json | 2 +- tests/unit_tests/corelay/__init__.py | 2 +- tests/unit_tests/corelay/io/__init__.py | 2 +- tests/unit_tests/corelay/io/test_storage.py | 40 +- tests/unit_tests/corelay/pipeline/__init__.py | 2 +- .../unit_tests/corelay/pipeline/test_base.py | 119 +- .../corelay/pipeline/test_spectral.py | 41 +- .../unit_tests/corelay/processor/__init__.py | 2 +- .../unit_tests/corelay/processor/test_base.py | 153 +- .../corelay/processor/test_clustering.py | 67 +- .../corelay/processor/test_embedding.py | 37 +- .../unit_tests/corelay/processor/test_flow.py | 16 +- .../corelay/processor/test_preprocessing.py | 28 +- tests/unit_tests/corelay/test_base.py | 19 +- tests/unit_tests/corelay/test_plugboard.py | 257 +-- tests/unit_tests/corelay/test_tracker.py | 14 +- tests/unit_tests/corelay/test_utils.py | 4 +- 67 files changed, 4435 insertions(+), 3297 deletions(-) create mode 100644 docs/examples/corelay_basics.py rename {example => docs/examples}/hdf5_structure.py (97%) rename {example => docs/examples}/memoize_spectral_pipeline.py (68%) rename {example => docs/examples}/virelay_analysis.py (77%) create mode 100644 docs/source/_static/custom.css create mode 100644 docs/source/api-reference/index.rst create mode 100644 docs/source/contributors-guide/contributing-code-or-documentation.rst create mode 100644 docs/source/contributors-guide/index.rst create mode 100644 docs/source/contributors-guide/reporting-issues-and-feature-requests.rst delete mode 100644 docs/source/getting-started.rst create mode 100644 docs/source/getting-started/basic-usage.rst create mode 100644 docs/source/getting-started/example-project.rst create mode 100644 docs/source/getting-started/index.rst create mode 100644 docs/source/getting-started/installation.rst create mode 100644 docs/source/images/spray-workflow.png create mode 100644 docs/source/migration-guide/index.rst create mode 100644 docs/source/migration-guide/migrating-from-v0.2-to-v0.3.rst delete mode 100644 docs/source/reference/index.rst delete mode 100644 example/corelay_basics.py diff --git a/.gitignore b/.gitignore index 0f7403b..3642daf 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,8 @@ npm-debug.log docs/build/ docs/doctree/ docs/source/_generated/ -docs/source/reference/* -!docs/source/reference/index.rst +docs/source/api-reference/* +!docs/source/api-reference/index.rst # Trained models and datasets/attributions/analyses *.h5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aa76db..aa2b605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,14 +81,30 @@ ### Documentation Updates in v0.3.0 +- The documentation was largely extended and a bit reorganized: + - The API reference documentation was moved from the `reference` directory to the `api-reference` directory. This was done to better reflect the purpose of the documentation, since there are also bibliographical references in the documentation. + - The "Getting Started" section was expanded to new section with multiple sub-sections: "Installation", "Basic Usage", and an "Example Project". + - The installation and basic usage sections were moved from the original "Getting Started" section. + - The "Example Project" section contains a more elaborate example of how to use CoRelAy to analyze a dataset generated by Zennit using the SpRAy workflow that can be visualized using ViRelAy. + - A new "Contributor's Guide" section was added with sub-sections on how to report issues and feature requests, and how to contribute code or documentation. + - A new "Migration Guide" section was added to help users migrate from CoRelAy v0.2 to CoRelAy v0.3. This was done, because the changes made in CoRelAy v0.3 are quite extensive and it is not easy to find out what has changed and how to adapt existing code to the new version. + - More citations were added to the documentation to relevant literature. - The configuration for Read the Docs was updated to use the latest available versions of Ubuntu (24.04) and Python (3.12). It was also documented. - The configuration for Sphinx was updated: + - The `sphinxcontrib.datatemplates` extension was removed, because it was not used in the documentation. + - Added the `sphinx.ext.intersphinx` extension to add links in the documentation to external documentations, e.g., the Python standard library documentation and the NumPy documentation. + - The docstrings in the CoRelAy source code now use `:py:*:` directives to link references to classes, functions, methods, modules, and values to their respective external documentation. - All warnings and errors that were reported by the linters were fixed and the configuration generally cleaned up to make it more readable and maintainable. - The `docs` environment in the tox configuration now uses Python 3.13.3, which causes an error during the building of the documentation, because the `pkg_resources` module was deprecated and removed in Python 3.12. This module was used to get the source code file paths and line numbers of functions, classes, methods, etc. for the `linkcode` extension. This was replaced by the `inspect` module. + - The invocation of Sphinx in tox to build the documentation was updated to include the `--fresh-env` option, so that the documentation is always built in a fresh environment. This solves some issues with the documentation build, where the documentation was not updated correctly after changes were made to the source code or the documentation itself. - The correct capitalization of the project name is now specified in the Sphinx configuration and the year in the copyright is now always set to the current year instead of hard-coding it. +- The example scripts were updated to reflect the coding style and docstring conventions used in the CoRelAy library. They were moved from the `example` directory to the `docs/examples` directory. This was done because the examples are part part of the documentation and to keep the root directory clean and free of unnecessary clutter. +- A custom CSS file was added to the documentation to change the alignment of the documentation text to be justified and to automatically break long words. This was done to improve the readability of the documentation and to align it with usual scientific documents. +- The width of the labels in the bibliography was set to a fixed size, so that the labels all have the same width. This was done, because they looked inconsistent and messy before. - The logo was added to the documentation, which previously contained a copy of the logo in the `docs/images` directory, but did not include it. The version contained in the `docs/images` directory was removed and the index page now directly references the logo in the `design` directory. - The favicon used in the documentation was updated to use the SVG version of the logo without the title. Previously, it was still the old "S" logo, which was from before CoRelAy was renamed from Sprincl. - One of the new dependency versions (`metrohash-python`) requires a C++ compiler to build and uses the `c++` command, which may not be available on all systems. To ensure that users are not confused by this, a note was added to the read me file explaining that the `c++` command is required to build the project and showing how to install it on Fedora (one of the systems that do not have the `c++` command installed by default). +- The read me of the repository was also updated. The introduction was extended to better explain the CoRelAy library. A section on the features of CoRelAy was added. The installation instructions and the usage section were now wrapped in a new "Getting Started" section. The example code in the usage section was updated to reflect the changes made to the example scripts. The "Contributing" section was also updated to include a proper description and links to the contributors guide in the documentation. ## v0.2.1 diff --git a/README.md b/README.md index 8f73b0d..e327796 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,18 @@ [![GitHub Release](https://img.shields.io/github/v/release/virelay/corelay)](https://github.com/virelay/corelay/releases/latest) [![PyPI Package](https://img.shields.io/pypi/v/corelay)](https://pypi.org/project/corelay/) -**CoRelAy** is a tool to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (`Task`) with default operations (`Processor`). Any step of the pipeline may then be individually changed by assigning a new operator (`Processor`). Processors have parameters (`Params`) which define their operation. - -**CoRelAy** was created to quickly implement pipelines to generate analysis data which can then be visualized using **ViRelAy**. +**CoRelAy** is a library designed for composing efficient, single-machine data analysis pipelines. It enables the rapid implementation of pipelines that can be used to analyze and process data. CoRelAy is primarily meant for the use in explainable artificial intelligence (XAI), often with the goal of producing output suitable for visualization in tools like [**ViRelAy**](https://github.com/virelay/virelay). +At the core of CoRelAy are **pipelines** (`Pipeline`), which consist of a series of **tasks** (`Task`). Each task is a modular unit that can be populated with **operations** (`Processor`) to perform specific data processing tasks. These operations, known as processors, can be customized by assigning new instances or modifying their default configurations. + +Tasks in CoRelAy are highly flexible and can be tailored to meet the needs of your analysis pipeline. By leveraging a wide range of configurable **processors** with their respective **parameters** (`Param`), you can easily adapt and optimize your data processing workflow. + +For more information about CoRelAy, getting started guides, in-depth tutorials, and API documentation, please refer to the [documentation](https://corelay.readthedocs.io/en/latest/). + If you find CoRelAy useful for your research, why not cite our related [paper](https://arxiv.org/abs/2106.13200): ```bibtex @@ -32,57 +36,62 @@ If you find CoRelAy useful for your research, why not cite our related [paper](h Müller, Klaus-Robert and Lapuschkin, Sebastian}, title = {Software for Dataset-wide XAI: From Local Explanations to Global Insights with {Zennit}, {CoRelAy}, and {ViRelAy}}, - journal = {CoRR}, - volume = {abs/2106.13200}, year = {2021}, + volume = {abs/2106.13200}, + journal = {CoRR} } ``` -## Documentation +## Features -The latest documentation is hosted at [corelay.readthedocs.io](https://corelay.readthedocs.io/en/latest/). +- **Pipeline Composition**: CoRelAy allows you to compose pipelines of processors, which can be executed in parallel or sequentially. +- **Task-based Design**: Each step in the pipeline is represented as a task, which can be easily modified or replaced. +- **Processor Library**: CoRelAy comes with a library of built-in processors for common tasks, such as clustering, embedding, and dimensionality reduction. +- **Memoization**: CoRelAy supports memoization of intermediate results, allowing you to reuse previously computed results and speed up your analysis. -## Install +## Getting Started -CoRelAy may be installed using pip with +### Installation + +To get started, you first have to install CoRelAy on your system. The recommended and easiest way to install CoRelAy is to use `pip`, the Python package manager. You can install CoRelAy using the following command: ```shell $ pip install corelay ``` > [!NOTE] -> If you experience issues installing the `metrohash-python` dependency, this may be due to the `c++` command being missing. For example, under Fedora, the `gcc-c++` package has to be installed to make the `c++` command available. You can install it using - -```shell -$ sudo dnf install gcc-c++ -``` +> CoRelAy depends on the [`metrohash-python`](https://pypi.org/project/metrohash-python/) library, which requires a C++ compiler to be installed. This may mean that you will have to install extra packages (GCC or Clang) for the installation to succeed. For example, on Fedora, you may have to install the `gcc-c++` package in order to make the `c++` command available, which can be done using the following command: +> +> ```shell +> $ sudo dnf install gcc-c++ +> ``` -To install optional HDBSCAN and UMAP support, use +To install CoRelAy with optional HDBSCAN and UMAP support, use ```shell $ pip install corelay[umap,hdbscan] ``` -## Usage +### Usage -Examples to highlight some features of **CoRelAy** can be found in `example/`. +Examples to highlight some features of CoRelAy can be found in [`docs/examples`](https://github.com/virelay/corelay/tree/main/docs/examples). -We mainly use HDF5 files to store results. The structure used by **ViRelAy** is documented in the **ViRelAy** repository at `docs/database_specification.md`. An example to create HDF5 files which can be used with **ViRelAy** is shown in `example/hdf5_structure.py` +We mainly use HDF5 files to store results. If you wish to visualize your analysis results using **ViRelAy**, please have a look at the [**ViRelAy documentation**](https://virelay.readthedocs.io/en/latest/contributors-guide/database-specification.html) to find out more about its database specification. An example to create HDF5 files which can be used with **ViRelAy** is shown in [`docs/examples/hdf5_structure.py`](https://github.com/virelay/corelay/tree/main/docs/examples/hdf5_structure.py). -To do a full SpRAy analysis which can be visualized with **ViRelAy**, an advanced script can be found in `example/virelay_analysis.py`. +To do a full SpRAy analysis which can be visualized with **ViRelAy**, an advanced script can be found in [`docs/examples/virelay_analysis.py`](https://github.com/virelay/corelay/tree/main/docs/examples/virelay_analysis.py). -The following shows the contents of `example/memoize_spectral_pipeline.py`: +The following shows the contents of [`docs/examples/memoize_spectral_pipeline.py`](https://github.com/virelay/corelay/tree/main/docs/examples/memoize_spectral_pipeline.py): ```python """An example script, which uses memoization to store (intermediate) results.""" import time +import typing from collections.abc import Sequence -from typing import Annotated, Any, SupportsIndex +from typing import Annotated, SupportsIndex import h5py import numpy -from numpy.typing import NDArray from corelay.base import Param from corelay.io.storage import HashedHDF5 @@ -94,62 +103,64 @@ from corelay.processor.flow import Sequential, Parallel class Flatten(Processor): - """Represents a CoRelAy processor, which flattens its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which flattens its input data.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Applies the flattening to the input data. Args: - data (Any): The input data that is to be flattened. + data (typing.Any): The input data that is to be flattened. Returns: - Any: Returns the flattened data. + typing.Any: Returns the flattened data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data input_data.sum() return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) class SumChannel(Processor): - """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which sums its input data across channels, i.e., its second axis.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Applies the summation over the channels to the input data. Args: - data (Any): The input data that is to be summed over its channels. + data (typing.Any): The input data that is to be summed over its channels. Returns: - Any: Returns the data that was summed up over its channels. + typing.Any: Returns the data that was summed up over its channels. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return input_data.sum(axis=1) class Normalize(Processor): - """Represents a CoRelAy processor, which normalizes its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which normalizes its input data.""" axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] - """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" + """A parameter of the :py:class:`~corelay.processor.base.Processor`, which determines the axis over which the data is to be normalized. Defaults + to the second and third axes. + """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Normalizes the specified input data. Args: - data (Any): The input data that is to be normalized. + data (typing.Any): The input data that is to be normalized. Returns: - Any: Returns the normalized input data. + typing.Any: Returns the normalized input data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return input_data / input_data.sum(self.axes, keepdims=True) def main() -> None: - """The entrypoint to the memoize_spectral_pipeline script.""" + """The entrypoint to the :py:mod:`memoize_spectral_pipeline` script.""" # Fixes the random seed for reproducibility numpy.random.seed(0xDEADBEEF) @@ -207,13 +218,11 @@ if __name__ == '__main__': ## Contributing -TODO: Add a section on how to contribute to the project. +If you would like to contribute, there are multiple ways you can help out. If you find a bug or have a feature request, please feel free to [open an issue on GitHub](https://github.com/virelay/corelay/issues). If you want to contribute code, please [fork the repository](https://github.com/virelay/corelay/fork) and use a feature branch. Pull requests are always welcome. Before forking, please open an issue where you describe what you want to do. This helps to align your ideas with ours and may prevent you from doing work, that we are already planning on doing. If you have contributed to the project, please add yourself to the [contributors list](https://github.com/virelay/corelay/blob/main/CONTRIBUTORS.md). -Installing the development dependencies can be done using the following command: +To help speed up the merging of your pull request, please comment and document your code extensively, try to emulate the coding style of the project, and update the documentation if necessary. -```shell -$ uv --directory source sync --all-extras -``` +For more information on how to contribute, please refer to our [contributor's guide](https://corelay.readthedocs.io/en/latest/contributors-guide/index.html). ## License diff --git a/docs/examples/corelay_basics.py b/docs/examples/corelay_basics.py new file mode 100644 index 0000000..808292a --- /dev/null +++ b/docs/examples/corelay_basics.py @@ -0,0 +1,127 @@ +"""An example script, which demonstrates the usage of CoRelAy's pipeline and processor classes.""" + +from types import FunctionType +from typing import Annotated, Any + +import numpy + +from corelay.base import Param +from corelay.pipeline.base import Pipeline, Task +from corelay.processor.affinity import Affinity, RadialBasisFunction +from corelay.processor.base import FunctionProcessor, Processor +from corelay.processor.distance import Distance, SciPyPDist + + +class MyProcessor(Processor): + """A custom CoRelAy processor, which applies a configurable function to its input data and multiplies it by a configurable value.""" + + multiplier: Annotated[int, Param(dtype=int, default=2)] + """An :py:class:`int` parameter, which is multiplied with the result of the function. + + Note: + Parameters can be registered with the processor, by defining a class attribute of type :py:class:`Annotated`, where the first argument is the + data type of the parameter, and the second is an instance of :py:class:`~corelay.base.Param`. The first argument to + :py:meth:`Param.__init__ ` is the runtime data type of the parameter (which may be different from the type hint + used as the first argument to :py:class:`Annotated`, e.g., the type hint may be a generic type like ``dict[int]``, while the runtime type must + be a concrete type like :py:class:`dict`, i.e., the same type that would be returned by :py:class:`type`). The second argument is the default + value of the parameter. + """ + + function_to_apply: Annotated[FunctionType, Param(FunctionType, lambda x: x**2)] + """A function, which is applied to the input data. + + Note: + As class methods have to be bound explicitly, :py:attr:`function_to_apply` here acts like a static function of :py:class:`MyProcessor`. If you + want to have processor that applies a custom function and that is bound to the class and has access to self, please refer to + :py:class:`~corelay.processor.base.FunctionProcessor`. + """ + + def function(self, data: Any) -> Any: + """Applies the custom function :py:attr:`function_to_apply` to the input data and multiplies it by the parameter :py:attr:`multiplier`. + + Args: + data (Any): The input data that is to be processed. + + Returns: + Any: Returns the processed data. + """ + + # Parameters can be accessed as self. + return self.multiplier * self.function_to_apply(data) + + +class MyPipeline(Pipeline): + """A custom CoRelAy pipeline, which applies a series of processors to its input data.""" + + pre_pre_process: Annotated[FunctionProcessor, Task(proc_type=FunctionProcessor, default=lambda self, x: x * 2, bind_method=True)] + """A pre-pre-processing task, which applies a function to the input data. By default, the input data is multiplied by 2. + + Note: + Tasks are registered by creating a class attribute of type :py:class:`Annotated`, with the first argument being the type of the processor that + is expected to be used in the task, and the second being an instance of :py:class:`~corelay.pipeline.base.Task`. The first argument to + :py:meth:`Task.__init__ ` is the type of the processor that is expected to be used in the task, and + the second argument is the default processor that is used by the task, if the user does not specify a custom processor. This can also be a + function, which will be converted to a :py:class:`~corelay.processor.base.FunctionProcessor`. Like parameters, the processors of the tasks can + be supplied to the :py:class:`Pipeline.__init__ ` method as keyword arguments with the same name as + the corresponding attribute. All additional keyword arguments that are passed to the :py:class:`~corelay.pipeline.base.Task` are passed to the + processor during instantiation. + + The :py:class:`~corelay.processor.base.FunctionProcessor` class is a :py:class:`~corelay.processor.base.Processor` that applies a customizable + function to the input data. By default, functions fed to :py:class:`~corelay.processor.base.FunctionProcessor` are not bound to the class. To + bind them, we can supply `bind_method=True` to the :py:class:`~corelay.processor.base.FunctionProcessor`. + """ + + pre_process: Annotated[FunctionProcessor, Task(proc_type=FunctionProcessor, default=lambda x: x**2)] + """A pre-processing task, which applies a function to the input data. By default, the input data is squared. + + Note: + The ``bind_method`` parameter of :py:class:`~corelay.processor.base.FunctionProcessor` is omitted here and therefore defaults to + :py:obj:`False`. This means that the function is not bound to the class and does not have access to `self`. + """ + + pairwise_distance: Annotated[Distance, Task(Distance, SciPyPDist(metric='sqeuclidean'))] + """A task, which applies a pairwise distance function to the input data. By default, the squared euclidean distance is used. The + :py:class:`~corelay.processor.distance.Distance` class is a base class for all distance processors. + """ + + affinity: Annotated[Affinity, Task(Affinity, RadialBasisFunction(sigma=1.0))] + """A task, which applies an affinity function to the input data. By default, the radial basis function is used. The + :py:class:`~corelay.processor.distance.Affinity` class is a base class for all affinity processors. + """ + + post_process: Annotated[Processor, Task()] + """A post-processing task, which does nothing by default and returns the input data as is.""" + + +def main() -> None: + """The entrypoint to the :py:mod:`corelay_basics` script.""" + + # Creates a new pipeline without specifying any parameters, which means that the default values of the tasks will be used + pipeline = MyPipeline() + first_output = pipeline(numpy.random.rand(5, 3)) + print('Pipeline output:', first_output) + + # Tasks are filled with processors during initialization of the Pipeline class; keyword arguments do not have to be in order, and if not supplied, + # the default value will be used + custom_pipeline = MyPipeline( + + # By setting the bind_method parameter to False, the function is not bound to the class and we do not need to a self argument + pre_pre_process=FunctionProcessor(processing_function=lambda x: x + 1, bind_method=False), + + # The pre_process task is set to a custom function, which is not of type Distance and is therefore automatically converted to a + # FunctionProcessor + pre_process=lambda x: x.mean(1), + + # The pairwise_distance task is omitted and therefore defaults to the squared euclidean distance; the affinity task is set to a + # RadialBasisFunction with a lower sigma value + affinity=RadialBasisFunction(sigma=0.1), + + # The empty post_process task is set to an instance of our custom processor MyProcessor and the multiplier parameter is set to 3 + post_process=MyProcessor(multiplier=3) + ) + second_output = custom_pipeline(numpy.ones((5, 3, 5))) + print('Custom pipeline output:', second_output) + + +if __name__ == '__main__': + main() diff --git a/example/hdf5_structure.py b/docs/examples/hdf5_structure.py similarity index 97% rename from example/hdf5_structure.py rename to docs/examples/hdf5_structure.py index 6527b37..2a605ba 100644 --- a/example/hdf5_structure.py +++ b/docs/examples/hdf5_structure.py @@ -229,9 +229,9 @@ def make_dataset_example() -> None: def main() -> None: - """The entrypoint to the hdf5_structure script, which generates two sets of sample HDF5 databases, one were the dataset samples and their - corresponding attributions are stored in HDF5 groups, and one were the dataset samples and their corresponding attributions are stored in HDF5 - datasets. + """The entrypoint to the :py:mod:`hdf5_structure` script, which generates two sets of sample HDF5 databases, one were the dataset samples and + their corresponding attributions are stored in HDF5 groups, and one were the dataset samples and their corresponding attributions are stored in + HDF5 datasets. """ make_group_example() diff --git a/example/memoize_spectral_pipeline.py b/docs/examples/memoize_spectral_pipeline.py similarity index 68% rename from example/memoize_spectral_pipeline.py rename to docs/examples/memoize_spectral_pipeline.py index 4bed4a7..8a8d564 100644 --- a/example/memoize_spectral_pipeline.py +++ b/docs/examples/memoize_spectral_pipeline.py @@ -3,12 +3,12 @@ # pylint: disable=duplicate-code import time +import typing from collections.abc import Sequence -from typing import Annotated, Any, SupportsIndex +from typing import Annotated, SupportsIndex import h5py import numpy -from numpy.typing import NDArray from corelay.base import Param from corelay.io.storage import HashedHDF5 @@ -20,62 +20,64 @@ class Flatten(Processor): - """Represents a CoRelAy processor, which flattens its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which flattens its input data.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Applies the flattening to the input data. Args: - data (Any): The input data that is to be flattened. + data (typing.Any): The input data that is to be flattened. Returns: - Any: Returns the flattened data. + typing.Any: Returns the flattened data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data input_data.sum() return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) class SumChannel(Processor): - """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which sums its input data across channels, i.e., its second axis.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Applies the summation over the channels to the input data. Args: - data (Any): The input data that is to be summed over its channels. + data (typing.Any): The input data that is to be summed over its channels. Returns: - Any: Returns the data that was summed up over its channels. + typing.Any: Returns the data that was summed up over its channels. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return input_data.sum(axis=1) class Normalize(Processor): - """Represents a CoRelAy processor, which normalizes its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which normalizes its input data.""" axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] - """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" + """A parameter of the :py:class:`~corelay.processor.base.Processor`, which determines the axis over which the data is to be normalized. Defaults + to the second and third axes. + """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Normalizes the specified input data. Args: - data (Any): The input data that is to be normalized. + data (typing.Any): The input data that is to be normalized. Returns: - Any: Returns the normalized input data. + typing.Any: Returns the normalized input data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return input_data / input_data.sum(self.axes, keepdims=True) def main() -> None: - """The entrypoint to the memoize_spectral_pipeline script.""" + """The entrypoint to the :py:mod:`memoize_spectral_pipeline` script.""" # Fixes the random seed for reproducibility numpy.random.seed(0xDEADBEEF) @@ -83,12 +85,12 @@ def main() -> None: # Opens an HDF5 file in append mode for the storing the results of the analysis and the memoization of intermediate pipeline results with h5py.File('test.analysis.h5', 'a') as analysis_file: - # Creates a HashedHDF5 IO object, which is an IO object that stores outputs of processors based on hashes in an HDF5 file + # Creates a HashedHDF5 IO object, which is storage container that stores outputs of processors based on hashes in an HDF5 file io_object = HashedHDF5(analysis_file.require_group('proc_data')) # Generates some exemplary data data = numpy.random.normal(size=(64, 3, 32, 32)) - number_of_clusters = range(2, 20) + numbers_of_clusters = range(2, 20) # Creates a SpectralClustering pipeline, which is one of the pre-defined built-in pipelines pipeline = SpectralClustering( @@ -100,7 +102,7 @@ def main() -> None: # processors; broadcast=False instead attempts to match each input to a processor clustering=Parallel([ Parallel([ - KMeans(n_clusters=k, io=io_object) for k in number_of_clusters + KMeans(n_clusters=number_of_clusters, io=io_object) for number_of_clusters in numbers_of_clusters ], broadcast=True), # IO objects will be used during computation when supplied to processors, if a corresponding output value (here identified by hashes) diff --git a/example/virelay_analysis.py b/docs/examples/virelay_analysis.py similarity index 77% rename from example/virelay_analysis.py rename to docs/examples/virelay_analysis.py index dfb616d..f01c920 100644 --- a/example/virelay_analysis.py +++ b/docs/examples/virelay_analysis.py @@ -2,14 +2,14 @@ # pylint: disable=duplicate-code -import json import argparse +import json +import typing from collections.abc import Sequence -from typing import Annotated, Any, SupportsIndex +from typing import Annotated, SupportsIndex import h5py import numpy -from numpy.typing import NDArray from scipy.spatial.distance import pdist, squareform from scipy.stats import pearsonr from skimage.metrics import structural_similarity # pylint: disable=no-name-in-module @@ -25,92 +25,92 @@ class Flatten(Processor): - """Represents a CoRelAy processor, which flattens its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which flattens its input data.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Applies the flattening to the input data. Args: - data (Any): The input data that is to be flattened. + data (typing.Any): The input data that is to be flattened. Returns: - Any: Returns the flattened data. + typing.Any: Returns the flattened data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) class SumChannel(Processor): - """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which sums its input data across channels, i.e., its second axis.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Applies the summation over the channels to the input data. Args: - data (Any): The input data that is to be summed over its channels. + data (typing.Any): The input data that is to be summed over its channels. Returns: - Any: Returns the data that was summed up over its channels. + typing.Any: Returns the data that was summed up over its channels. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return input_data.sum(axis=1) class Absolute(Processor): - """Represents a CoRelAy processor, which computes the absolute value of its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which computes the absolute value of its input data.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the absolute value of the specified input data. Args: - data (Any): The input data for which the absolute value is to be computed. + data (typing.Any): The input data for which the absolute value is to be computed. Returns: - Any: Returns the absolute value of the input data. + typing.Any: Returns the absolute value of the input data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return numpy.absolute(input_data) class Normalize(Processor): - """Represents a CoRelAy processor, which normalizes its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which normalizes its input data.""" axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Normalizes the specified input data. Args: - data (Any): The input data that is to be normalized. + data (typing.Any): The input data that is to be normalized. Returns: - Any: Returns the normalized input data. + typing.Any: Returns the normalized input data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return input_data / input_data.sum(self.axes, keepdims=True) class Histogram(Processor): - """Represents a CoRelAy processor, which computes a histogram over its input data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which computes a histogram over its input data.""" bins: Annotated[int, Param(int, 256)] """A parameter of the processor, which determines the number of bins that are used to compute the histogram.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes histograms over the specified input data. One histogram is computed for each channel and each sample in a batch of input data. Args: - data (Any): The input data over which the histograms are to be computed. + data (typing.Any): The input data over which the histograms are to be computed. Returns: - Any: Returns the histograms that were computed over the input data. + typing.Any: Returns the histograms that were computed over the input data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return numpy.stack([ numpy.stack([ numpy.histogram( @@ -122,22 +122,22 @@ def function(self, data: Any) -> Any: class SSIM(Processor): - """Represents a CoRelAy processor, which computes the structural similarity index (SSIM) of the data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which computes the structural similarity index (SSIM) of the data.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the SSIM of the specified input data. Args: - data (Any): The input data for which the SSIM is to be computed. Each channel of the input data is treated as a separate sample and the - SSIM is computed between each pair of samples. The input data is expected to have the shape `(number_of_samples, height, width)`. + data (typing.Any): The input data for which the SSIM is to be computed. Each channel of the input data is treated as a separate sample and + the SSIM is computed between each pair of samples. The input data is expected to have the shape `(number_of_samples, height, width)`. Returns: - Any: Returns a square distance matrix, where each element i, j contains the SSIM between the samples i and j. + typing.Any: Returns a square distance matrix, where each element `i`, `j` contains the SSIM between the samples `i` and `j`. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data number_of_samples, height, width = input_data.shape - distance_matrix: NDArray[Any] = pdist( + distance_matrix: numpy.ndarray[typing.Any, typing.Any] = pdist( input_data.reshape(number_of_samples, height * width), metric=lambda x, y: structural_similarity(x.reshape(height, width), y.reshape(height, width)) # type: ignore[no-untyped-call] ) @@ -145,41 +145,41 @@ def function(self, data: Any) -> Any: class PCC(Processor): - """Represents a CoRelAy processor, which computes the Pearson correlation coefficient (PCC) of the data.""" + """Represents a :py:class:`~corelay.processor.base.Processor`, which computes the Pearson correlation coefficient (PCC) of the data.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the PCC of the specified input data. Args: - data (Any): The input data for which the PCC is to be computed. This must be a NumPy array of samples of shape + data (typing.Any): The input data for which the PCC is to be computed. This must be a :py:class:`~numpy.ndarray` of samples of shape `(number_of_samples, number_of_dimensions)`, in `number_of_dimensions` dimensional space. Returns: - Any: Returns a NumPy array, which contains a square distance matrix, where each element i, j contains the PCC between the samples i and j. + typing.Any: Returns a :py:class:`~numpy.ndarray`, which contains a square distance matrix, where each element `i`, `j` contains the PCC + between the samples `i` and `j`. """ - def pearsonr_distance(x: NDArray[Any], y: NDArray[Any]) -> float: + def pearsonr_distance(x: numpy.ndarray[typing.Any, typing.Any], y: numpy.ndarray[typing.Any, typing.Any]) -> float: """Computes the Pearson correlation coefficient between two samples. Args: - x (NDArray[Any]): The first sample. - y (NDArray[Any]): The second sample. + x (numpy.ndarray[typing.Any, typing.Any]): The first sample. + y (numpy.ndarray[typing.Any, typing.Any]): The second sample. Returns: - float: The Pearson correlation coefficient between the two samples. + float: Returns the Pearson correlation coefficient between the two samples. """ - p_value: NDArray[numpy.floating] | float = pearsonr(x, y).statistic + p_value: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] | float = pearsonr(x, y).statistic if isinstance(p_value, numpy.ndarray): return p_value.item() return p_value - input_data: NDArray[Any] = data - distance_matrix: NDArray[numpy.floating] = pdist(input_data, metric=pearsonr_distance) + input_data: numpy.ndarray[typing.Any, typing.Any] = data + distance_matrix: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating]] = pdist(input_data, metric=pearsonr_distance) return squareform(distance_matrix) -# Contains the various pre-processing method and distance metric variants that can be used to compute the analysis VARIANTS = { 'absspectral': { 'preprocessing': Sequential([ @@ -229,6 +229,7 @@ def pearsonr_distance(x: NDArray[Any], y: NDArray[Any]) -> float: 'distance': PCC(), } } +"""Contains the various pre-processing method and distance metric variants that can be used in the analysis.""" def meta_analysis( @@ -269,16 +270,10 @@ def meta_analysis( affinity=SparseKNN(n_neighbors=number_of_neighbors, symmetric=True), embedding=EigenDecomposition(n_eigval=number_of_eigenvalues, is_output=True), clustering=Parallel([ - Parallel([ - KMeans(n_clusters=number_of_clusters) for number_of_clusters in number_of_clusters_list - ], broadcast=True), - Parallel([ - DBSCAN(eps=number_of_clusters / 10.0) for number_of_clusters in number_of_clusters_list - ], broadcast=True), + Parallel([KMeans(n_clusters=number_of_clusters) for number_of_clusters in number_of_clusters_list], broadcast=True), + Parallel([DBSCAN(eps=number_of_clusters / 10.0) for number_of_clusters in number_of_clusters_list], broadcast=True), HDBSCAN(), - Parallel([ - AgglomerativeClustering(n_clusters=number_of_clusters) for number_of_clusters in number_of_clusters_list - ], broadcast=True), + Parallel([AgglomerativeClustering(n_clusters=number_of_clusters) for number_of_clusters in number_of_clusters_list], broadcast=True), Parallel([ UMAPEmbedding(), TSNEEmbedding(), @@ -304,7 +299,7 @@ def meta_analysis( # Gets the indices of the classes for which the meta-analysis is to be performed, if non were specified, the meta-analysis is performed for all # classes if class_indices is None: - class_indices = [int(label['index']) for label in label_map] + class_indices = [label['index'] for label in label_map] # Truncate the analysis database print(f'Truncating {analysis_file_path}') @@ -336,7 +331,7 @@ def meta_analysis( # Adds the indices of the samples in the current class to the analysis database analysis_group = analysis_file.require_group(analysis_name) - analysis_group['index'] = indices_of_samples_in_class.astype('uint32') + analysis_group['index'] = indices_of_samples_in_class.astype(numpy.uint32) # Adds the spectral embedding to the analysis database embedding_group = analysis_group.require_group('embedding') @@ -348,7 +343,7 @@ def meta_analysis( embedding_group['tsne'].attrs['embedding'] = 'spectral' embedding_group['tsne'].attrs['index'] = numpy.array([0, 1]) - # Adds the uMap embedding to the analysis database + # Adds the UMAP embedding to the analysis database embedding_group['umap'] = umap.astype(numpy.float32) embedding_group['umap'].attrs['embedding'] = 'spectral' embedding_group['umap'].attrs['index'] = numpy.array([0, 1]) @@ -401,7 +396,7 @@ def meta_analysis( def main() -> None: - """The entrypoint to the virelay_analysis script.""" + """The entrypoint to the :py:mod:`virelay_analysis` script.""" argument_parser = argparse.ArgumentParser( prog='virelay_analysis', diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css new file mode 100644 index 0000000..a54b029 --- /dev/null +++ b/docs/source/_static/custom.css @@ -0,0 +1,16 @@ + +/* Justifies the text in the documentation articles, which makes them easier to read; words are automatically hyphenated */ +section > p, +section > ol > li > p, +section > ul > li > p, +section > .admonition > p:not(.admonition-title), +#bibliography .citation p { + text-align: justify; + hyphens: auto; +} + +/* Sets the width of the labels in the bibliography to a fixed size, because otherwise, the labels are all different sizes, which makes the + bibliography look ragged and messy */ +#bibliography .citation .label { + width: 10rem; +} diff --git a/docs/source/_templates/autosummary/module.rst b/docs/source/_templates/autosummary/module.rst index 5525a58..b80d489 100644 --- a/docs/source/_templates/autosummary/module.rst +++ b/docs/source/_templates/autosummary/module.rst @@ -1,70 +1,72 @@ -{{ fullname | escape | underline}} +{{ fullname | escape | underline }} .. automodule:: {{ fullname }} - :members: :show-inheritance: + :members: + :special-members: __bool__, __call__, __delete__, __delete_attr__, __dir__, __enter__, __exit__, __get__, __get_attr__, __getitem__, __new__, __prepare__, __repr__, __set__, __set_attr__, __set_name__, __setitem__, __tracked__ {% block attributes %} - {% if attributes %} - .. rubric:: {{ _('Module Attributes') }} + {%- if attributes %} + .. rubric:: Module Attributes .. autosummary:: - :nosignatures: + :signatures: none {% for item in attributes %} {{ item }} {%- endfor %} {% endif %} - {% endblock %} + {%- endblock %} - {% block functions %} - {% if functions %} - .. rubric:: {{ _('Functions') }} + {%- block functions %} + {%- if functions %} + .. rubric:: Functions .. autosummary:: - :nosignatures: + :signatures: none {% for item in functions %} {{ item }} {%- endfor %} {% endif %} - {% endblock %} + {%- endblock %} - {% block classes %} - {% if classes %} - .. rubric:: {{ _('Classes') }} + {%- block classes %} + {%- if classes %} + .. rubric:: Classes .. autosummary:: - :nosignatures: + :signatures: none {% for item in classes %} {{ item }} {%- endfor %} {% endif %} - {% endblock %} + {%- endblock %} - {% block exceptions %} - {% if exceptions %} - .. rubric:: {{ _('Exceptions') }} + {%- block exceptions %} + {%- if exceptions %} + .. rubric:: Exceptions .. autosummary:: - :nosignatures: + :signatures: none {% for item in exceptions %} {{ item }} {%- endfor %} {% endif %} - {% endblock %} + {%- endblock %} -{% block modules %} -{% if modules %} +{%- block modules %} +{%- if modules %} .. rubric:: Modules .. autosummary:: :toctree: :recursive: + {% for item in modules %} {{ item }} {%- endfor %} {% endif %} -{% endblock %} +{%- endblock %} diff --git a/docs/source/api-reference/index.rst b/docs/source/api-reference/index.rst new file mode 100644 index 0000000..b92a762 --- /dev/null +++ b/docs/source/api-reference/index.rst @@ -0,0 +1,19 @@ +============= +API Reference +============= + +The following section provides an exhaustive documentation of CoRelAy's constituent modules, classes, methods, and functions. It offers in-depth descriptions of their respective functionalities, parameter details, and usage examples. + +.. autosummary:: + :toctree: + :nosignatures: + :recursive: + + corelay.base + corelay.io + corelay.pipeline + corelay.plugboard + corelay.processor + corelay.tracker + corelay.utils + diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib index 4592128..730a14e 100644 --- a/docs/source/bibliography.bib +++ b/docs/source/bibliography.bib @@ -1,4 +1,67 @@ +@article{vonLuxburg2007tutorial, + author = {von Luxburg, Ulrike}, + title = {A tutorial on spectral clustering}, + year = {2007}, + month = {Dec}, + day = {01}, + doi = {10.1007/s11222-007-9033-z}, + issn = {1573-1375}, + volume = {17}, + number = {4}, + pages = {395-416}, + url = {https://doi.org/10.1007/s11222-007-9033-z}, + journal = {Statistics and Computing} +} + +@article{bach2015pixel, + author = {Bach, Sebastian and + Binder, Alexander and + Montavon, Grégoire and + Klauschen, Frederick and + Müller, Klaus-Robert and + Samek, Wojciech}, + title = {On Pixel-wise Explanations for Non-Linear Classifier Decisions by Layer-wise Relevance Propagation}, + year = {2015}, + volume = {10}, + number = {7}, + pages = {e0130140}, + journal = {PLoS ONE} +} + +@incollection{montavon2019layer, + author = {Montavon, Grégoire and + Binder, Alexander and + Lapuschkin, Sebastian and + Samek, Wojciech and + Müller, Klaus-Robert}, + title = {Layer-wise Relevance Propagation: An Overview}, + year = {2019}, + booktitle = {Explainable AI: Interpreting, Explaining and Visualizing Deep Learning}, + pages = {193--209}, + publisher = {Springer LNCS 11700} +} + +@article{lapuschkin2019spray, + author = {Lapuschkin, Sebastian and + Wäldchen, Stephan and + Binder, Alexander and + Montavon, Grégoire and + Samek, Wojciech and + Müller, Klaus-Robert}, + title = {Unmasking Clever Hans predictors and assessing what machines really learn}, + year = {2019}, + month = {Mar}, + day = {11}, + doi = {10.1038/s41467-019-08987-4}, + issn = {2041-1723}, + volume = {10}, + number = {1}, + pages = {1096}, + url = {https://doi.org/10.1038/s41467-019-08987-4}, + journal = {Nature Communications} +} + @article{anders2021software, author = {Anders, Christopher J. and Neumann, David and @@ -6,7 +69,7 @@ @article{anders2021software Müller, Klaus-Robert and Lapuschkin, Sebastian}, title = {Software for Dataset-wide XAI: From Local Explanations to Global Insights with {Zennit}, {CoRelAy}, and {ViRelAy}}, - journal = {CoRR}, - volume = {abs/2106.13200}, year = {2021}, + volume = {abs/2106.13200}, + journal = {CoRR} } diff --git a/docs/source/conf.py b/docs/source/conf.py index 804112a..d0962aa 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,14 +4,14 @@ import inspect import os -import re import sys +import typing from collections.abc import Iterator, Sequence from datetime import datetime from subprocess import run, CalledProcessError from types import ModuleType -from typing import Any, Literal +import h5py from pybtex.database import Entry from pybtex.plugin import register_plugin from pybtex.style.formatting.plain import Style as PlainStyle @@ -19,10 +19,6 @@ from sphinx.application import Sphinx -LanguageDomain = Literal['py', 'c', 'cpp', 'javascript'] -"""Represents the different language domains for which the linkcode extension can generate links to the source in the repository.""" - - class AuthorYearLabelStyle(BaseLabelStyle): """Represents a citation label style, which uses the format "[ et al., ]".""" @@ -67,7 +63,7 @@ def get_latest_git_tag() -> str: return 'main' -def get_object_by_name(name: str, module: ModuleType) -> ModuleType | type[Any] | None: +def get_object_by_name(name: str, module: ModuleType) -> ModuleType | type[typing.Any] | None: """Retrieves the object with the specified name from the specified module. The object can be a module, a class, a method, a function, or a variable. If the name is not valid or the object does not exist, then None is returned. @@ -76,15 +72,15 @@ def get_object_by_name(name: str, module: ModuleType) -> ModuleType | type[Any] module (ModuleType): The module from which to retrieve the object. Returns: - ModuleType | type[Any] | None: Returns the object with the specified name from the specified module. If the object does not exist, then - None is returned. + ModuleType | type[typing.Any] | None: Returns the object with the specified name from the specified module. If the object does not exist, then + None is returned. """ name_components = name.split('.') if not name_components: return None - object_to_retrieve: ModuleType | type[Any] = module + object_to_retrieve: ModuleType | type[typing.Any] = module for name_component in name_components: try: object_to_retrieve = getattr(object_to_retrieve, name_component) @@ -103,7 +99,8 @@ def get_top_level_module(module_name_hierarchy: str) -> ModuleType | None: module_name_hierarchy (str): The module name hierarchy from which the top-level module is to be retrieved. Returns: - ModuleType | None: Returns the top-level module from the specified module name. If the module does not exist, then None is returned. + ModuleType | None: Returns the top-level module from the specified module name. If the module does not exist, then + :py:obj:`None` is returned. """ module_hierarchy_names = module_name_hierarchy.split('.') @@ -118,71 +115,18 @@ def get_top_level_module(module_name_hierarchy: str) -> ModuleType | None: return top_module -def is_type_alias_for_literal(object_to_check: Any) -> bool: - """Checks if the specified object is a type alias for a Literal[...] type. - - Args: - object_to_check (Any): The object to check. - - Returns: - bool: Returns True if the specified object is a type alias for a Literal[...] type, otherwise False. - """ - - return type(object_to_check).__name__ == '_LiteralGenericAlias' - - -def get_source_code_location_of_literal_type_alias(name: str, module: ModuleType) -> tuple[str | None, int | None, int | None]: - """Retrieves the path to the source code file that contains the type alias for the Literal[...] type, as well as the start and end line numbers of - the type alias declaration. If the type alias for the Literal[...] type does not exist, then None is returned for all three values. - - Args: - name (str): The name of the type alias for the Literal[...] type. - module (ModuleType): The module that contains the type alias for the Literal[...] declaration. - - Returns: - tuple[str | None, int | None, int | None]: Returns a tuple containing the path to the source code file that contains the type alias for the - Literal[...] type, as well as the start and end line numbers of the type alias declaration. If the type alias for the Literal[...] type - does not exist, then None is returned for all three values. - """ - - # Checks if the module's file path is available, if not, then there is no source file that we can check - if module.__file__ is None: - return None, None, None - - # Loads the module's source code file into memory - with open(module.__file__, mode='r', encoding='utf-8') as module_file: - module_file_contents = module_file.read() - - # Checks if the type alias declaration is present in the module's source code file, if not then we cannot generate a URL to the source code - match: re.Match[str] | None = re.search( - r'(type \s*)?' + name + r'\s* (: \s* TypeAlias)? \s* = \s* Literal \s* \[ \s* (\s* [\'"] [^\'"]* [\'"] ,?)* \s* \]', - module_file_contents, - re.VERBOSE - ) - if not match: - return None, None, None - - # Gets the start and the end line number of the type alias declaration - start_index, end_index = match.span() - start_line_number = module_file_contents.count('\n', 0, start_index) + 1 - end_line_number = module_file_contents.count('\n', 0, end_index) + 1 - - # Returns the path to the source code file that contains the type alias declaration, and the start and end line numbers of the declaration - return module.__file__, start_line_number, end_line_number - - -def get_source_code_location_of_object(object_to_get_location_for: ModuleType | type[Any]) -> tuple[str | None, int | None, int | None]: +def get_source_code_location_of_object(object_to_get_location_for: ModuleType | type[typing.Any]) -> tuple[str | None, int | None, int | None]: """Retrieves the path to the source code file that contains the specified object, as well as the start and end line numbers of the object's definition. The object can be a module, a class, a method, a or function. If the source code file path is not available, or the object's - definition cannot be found, then None is returned for all three values. + definition cannot be found, then :py:obj:`None` is returned for all three values. Args: - object_to_get_location_for (ModuleType | type[Any]): The object for which the source code location is to be retrieved. + object_to_get_location_for (ModuleType | type[typing.Any]): The object for which the source code location is to be retrieved. Returns: - tuple[str | None, int | None, int | None]: Returns a tuple containing the path to the source code file that contains the specified object, - as well as the start and end line numbers of the object's definition. If the source code file path is not available, or the object's - definition cannot be found, then None is returned for all three values. + tuple[str | None, int | None, int | None]: Returns a :py:class:`tuple` containing the path to the source code file that contains the specified + object, as well as the start and end line numbers of the object's definition. If the source code file path is not available, or the object's + definition cannot be found, then :py:obj:`None` is returned for all three values. """ # Gets the absolute path to the source code file that contains the object; if the object is not a module, class, method, function, traceback, @@ -210,27 +154,28 @@ def get_source_code_location_of_object(object_to_get_location_for: ModuleType | return None, None, None -def linkcode_resolve(domain: LanguageDomain, info: dict[str, str]) -> str | None: +def linkcode_resolve(domain: str, info: dict[str, str]) -> str | None: """Determines the URL to the source code corresponding to the object in the specified domain with the provided information. This function was adapted from https://gist.github.com/nlgranger/55ff2e7ff10c280731348a16d569cb73. Args: - domain (LanguageDomain): Specified the language domain the object is in. Can be one of "py", "c", "cpp", or "javascript". - info (dict[str, str]): A dictionary, which contains further information about the object for which the URL is to be retrieved. Depending on - the domain, the following keys are guaranteed to be present: - - domain is "py": - - "module", which contains the name of the module. - - "fullname", which contains the full name of the object. - - domain is "c": - - "names", which contains a list of names for the object. - - domain is "cpp": - - "names", which contains a list of names for the object. - - domain is "javascript": - - "object", which is the name of the object. - - "fullname", which is the name of the item. + domain (str): Specified the language domain the object is in. Can be one of "py", "c", "cpp", or "javascript". + info (dict[str, str]): A :py:class:`dict`, which contains further information about the object for which the URL is to be retrieved. Depending + on the domain, the following keys are guaranteed to be present: + + * domain is "py": + * "module", which contains the name of the module. + * "fullname", which contains the full name of the object. + * domain is "c": + * "names", which contains a list of names for the object. + * domain is "cpp": + * "names", which contains a list of names for the object. + * domain is "javascript": + * "object", which is the name of the object. + * "fullname", which is the name of the item. Returns: - str | None: Returns the URL of the source code corresponding to the specified object. If no URL is available, then None is returned. + str | None: Returns the URL of the source code corresponding to the specified object. If no URL is available, then :py:obj:`None` is returned. """ # We are only interested in Python source code URLs @@ -243,19 +188,12 @@ def linkcode_resolve(domain: LanguageDomain, info: dict[str, str]) -> str | None return None # Gets the object for which the source code URL is to be retrieved - object_to_get_url_for: ModuleType | type[Any] | None = get_object_by_name(info['fullname'], module) + object_to_get_url_for: ModuleType | type[typing.Any] | None = get_object_by_name(info['fullname'], module) if object_to_get_url_for is None: return None - # Unfortunately, type aliases for Literal[...]'s (such as LanguageDomain) are not supported by the getsourcefile function, because they are not - # real types, but objects of the typing._LiteralGenericAlias class, and getsourcefile only supports modules, classes, methods, and functions; - # therefore, the only option we have is that we find the declaration of the type alias in the file of the module it is located in and then use - # the line number of the type alias declaration to generate the URL to the source code file on GitHub; otherwise, we can use the getsourcefile and - # getsourcelines functions to generate the URL to the source code file on GitHub - if is_type_alias_for_literal(object_to_get_url_for): - object_file_path, start_line_number, end_line_number = get_source_code_location_of_literal_type_alias(info['fullname'], module) - else: - object_file_path, start_line_number, end_line_number = get_source_code_location_of_object(object_to_get_url_for) + # Gets the position of the object in the source code file + object_file_path, start_line_number, end_line_number = get_source_code_location_of_object(object_to_get_url_for) # If the source code file path, the start line number, or the end line number is not available, then we cannot generate a URL to the source code if object_file_path is None or start_line_number is None or end_line_number is None: @@ -293,6 +231,18 @@ def setup(app: Sphinx) -> None: ) +# Enables the reporting of all warnings as errors, which is useful to ensure that there are no hidden issues in the documentation, like missing +# reference targets; some of the warnings cannot be avoided, so they are silenced to avoid that the build fails (when warnings are issued, but the +# build is successful, Sphinx will still return a non-zero exit code, which would cause the CI/CD pipeline to fail) +nitpicky = True +nitpick_ignore = [ + ('py:class', 'FunctionType'), + ('py:class', 'LambdaType'), + ('py:class', 'metrohash.MetroHash128'), + ('py:class', 'types.FunctionType'), + ('py:class', 'types.LambdaType') +] + # Sets the basic project information project = 'CoRelAy' project_copyright = f'{datetime.now().year}, CoRelAy' @@ -302,6 +252,7 @@ def setup(app: Sphinx) -> None: templates_path = ['_templates'] html_static_path = ['_static'] html_favicon = '_static/favicon.svg' +html_css_files = ['custom.css'] # Specifies a list of patterns, relative to source directory, that match files and directories to ignore when looking for source files, in this case, # nothing needs to be excluded @@ -311,30 +262,58 @@ def setup(app: Sphinx) -> None: extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', + 'sphinx.ext.intersphinx', 'sphinx.ext.extlinks', 'sphinx.ext.linkcode', 'sphinx.ext.mathjax', 'sphinx.ext.napoleon', 'sphinxcontrib.bibtex', - 'sphinxcontrib.datatemplates', 'sphinx_copybutton', 'sphinx_rtd_theme' ] +# H5PY keeps the actual classes in a separate private module called _hl and re-exports it in the main module, so Sphinx AutoDoc will always raise a +# warning that the references to the classes are not found; this is a workaround, which allows Sphinx AutoDoc to find the references correctly +h5py.File.__module__ = 'h5py' +h5py.Dataset.__module__ = 'h5py' +h5py.Group.__module__ = 'h5py' + +# Configures the sphinx.ext.autodoc plugin, which is used to generate documentation from docstrings; this specifies that (1) classes and their +# constructor signature are separated, i.e., the class signature only specifies the name of the class and the constructor is presented as a separate +# method, (2) the members of a module or a class are ordered in the same way they are ordered in the source code, (3) the type hints are displayed +# both in the signature of a function/method, as well as in the description, and (4) the default values of function/method parameters are preserved as +# they are in the source code, instead of evaluating them to their actual values and then displaying the values in the documentation +autodoc_class_signature = 'separated' +autodoc_member_order = 'bysource' +autodoc_typehints = 'both' +autodoc_preserve_defaults = True + # Registers the pybtex.style.formatting plugin, which is used to format citations of bibliography entries to the format # "[ et al., ]" register_plugin('pybtex.style.formatting', 'author_year_style', AuthorYearStyle) -# Configures the Sphinx plugin, which adds a copy button to code blocks +# Configures the sphinx_copybutton plugin, which adds a copy button to code blocks copybutton_prompt_text = r'>>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: ' copybutton_prompt_is_regexp = True copybutton_line_continuation_character = '\\' copybutton_here_doc_delimiter = 'EOT' -# Configures the theme of the HTML pages, which is set to the Read the Docs theme that was installed as a plugin -html_theme = 'sphinx_rtd_theme' +# Configures the sphinx.ext.intersphinx plugin, which allows the documentation to link to the Sphinx documentation of other projects; Sphinx defaults +# to automatically resolve unresolved labels using the Intersphinx mappings; this behavior has unintended side-effects, namely that documentations +# local references can suddenly resolve to an external location; therefore, the intersphinx_disabled_reftypes config value is set to ["*"], which +# disables the automatic resolution of unresolved labels using the Intersphinx mappings; this is recommended by the Read the Docs documentation +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable', None), + 'h5py': ('https://docs.h5py.org/en/stable', None), + 'scipy': ('https://docs.scipy.org/doc/scipy', None), + 'scikit-learn': ('https://scikit-learn.org/stable', None), + 'hdbscan': ('https://hdbscan.readthedocs.io/en/latest', None), + 'pytorch': ('https://docs.pytorch.org/docs/stable', None) +} -# Configures the Sphinx plugin, which allows us to insert external links to the GitHub repository using ":repo:`file-path`" instead of the full URL +# Configures the sphinx.ext.extlinks plugin, which allows us to insert external links to the GitHub repository using ":repo:`file-path`" instead of +# the full URL LATEST_GIT_TAG = get_latest_git_tag() extlinks = { 'repo': ( @@ -343,7 +322,10 @@ def setup(app: Sphinx) -> None: ) } -# Configures the Sphinx plugin, which allows BibTeX citations to be inserted into documentation +# Configures the sphinxcontrib.bibtex plugin, which allows BibTeX citations to be inserted into documentation bibtex_bibfiles = ['bibliography.bib'] bibtex_default_style = 'author_year_style' bibtex_reference_style = 'author_year' + +# Configures the theme of the HTML pages, which is set to the Read the Docs theme that was installed via the sphinx_rtd_theme plugin +html_theme = 'sphinx_rtd_theme' diff --git a/docs/source/contributors-guide/contributing-code-or-documentation.rst b/docs/source/contributors-guide/contributing-code-or-documentation.rst new file mode 100644 index 0000000..d3bd89c --- /dev/null +++ b/docs/source/contributors-guide/contributing-code-or-documentation.rst @@ -0,0 +1,176 @@ +================================== +Contributing Code or Documentation +================================== + +Before contributing code or documentation, please ensure that there is no existing issue that aligns with your ideas. This helps avoid duplication of effort and ensures a seamless integration of your contributions into the project. If you have an idea for a contribution but can't find a related issue, we encourage you to open an issue, where you outline your proposal, before starting your work. This facilitates alignment with the rest of the team and may prevent unnecessary work. + +To contribute code or documentation, follow these steps: + +1. Fork the Repository +====================== + +To begin contributing to this project, you will first need to `fork the repository on GitHub `_. This creates a copy of the original repository under your own account. After forking the repository, it can be cloned using the following command: + +.. code-block:: console + + $ git clone https://github.com//corelay.git + +CoRelAy leverages `uv `_, a Python package and project manager, to streamline its development lifecycle. For detailed instructions on installing and utilizing ``uv``, please refer to its `comprehensive documentation `_. This tool enables the efficient installation of supported Python versions, management of virtual environments, handling of runtime and development dependencies, and project building. + +Following the installation of ``uv``, please proceed to install the supported Python versions, which comprise 3.11, 3.12, and 3.13, as specified in the :repo:`source/.python-versions` file. This can be accomplished via the following command: + +.. code-block:: console + + $ uv --directory source python install + +Upon installing the supported Python versions, dependencies must be installed via the ``uv sync`` command. These are specified within the :repo:`source/pyproject.toml` project configuration file and constrained to specific versions in the :repo:`source/uv.lock` lock file. To install these dependencies, execute the following command: + +.. code-block:: console + + $ uv --directory source sync --all-extras + +To start your work, please create a new branch specifically for your feature or bug fix. When naming your branch, we recommend using kebab-case (lowercase words separated by hyphens) to clearly describe its purpose, e.g., ``my-new-feature``. + +2. Make your Changes +==================== + +Before making any changes to the codebase, please familiarize yourself with the project by reading the :doc:`getting started guide <../getting-started/index>`, which will help you understand how the project works. To contribute to this project, please make the necessary changes to the codebase while adhering to our established coding standards and guidelines. We recommend extensively documenting your code with comments. We follow the `Google-style Docstring `_ convention. + +When modifying existing code, please refrain from altering the project's coding style unless absolutely necessary. Changes to the code style can lead to unnecessary effort and frequent updates to accommodate varying preferences. To ensure consistency and maintainability, we utilize various tools to enforce our coding conventions, detect potential issues, prevent bugs, and statically type-check CoRelAy: + +* `PyLint `_ -- Enforces coding style and detects potential issues and bugs. +* `PyCodeStyle `_ -- Verifies code style consistency. +* `PyDocLint `_ -- Ensures proper documentation and adherence to Docstring style guidelines. +* `MyPy `_ -- Statically type-checks the code. + +Before committing your changes, please verify that none of these tools produce warnings. Additionally, we advise against modifying the configuration of these tools unless a compelling reason exists (please provide details on your reasoning in the accompanying issue or pull request). For more information, please refer to the :ref:`testing-and-linting` section. + +3. Write Unit Tests +=================== + +To ensure the reliability and stability of our codebase, please write comprehensive unit tests for the features you have added. Our goal is to achieve 100% test coverage for CoRelAy. This means that every line of code should be executed at least once during testing. Before committing your changes, please not only ensure that all unit tests pass without errors or failures, but also write sensible unit tests for all changes that you have made. This ensures that our codebase remains robust and maintains its expected functionality. + +4. Update the Documentation +=========================== + +If your changes have impacted how the project is used or you made changes to its functionality, please ensure that the relevant sections of our documentation are updated accordingly. We use `Sphinx `_ to generate our documentation, which can be found in the :repo:`docs/source` directory. + +A local build of the documentation can be created using the following command: + +.. code-block:: console + + $ uv --directory source run tox -e docs + +.. _testing-and-linting: + +5. Testing & Linting +==================== + +We use ``tox`` to run unit tests, linters and static type checkers on CoRelAy, as well as to build the documentation. If you've made any changes to CoRelAy or the documentation that require updates to configurations of the linters, type checker, or ``tox``, please ensure that the relevant sections in the following configuration files are are revised accordingly: + +* **tox** -- :repo:`source/tox.ini` +* **PyLint** -- :repo:`tests/linters/.pylintrc` +* **PyCodeStyle** -- :repo:`tests/linters/.pycodestyle` +* **PyDocLint** -- :repo:`tests/linters/.pydoclint.toml` +* **MyPy** -- :repo:`tests/linters/.mypy.ini` + +To run tests and build the documentation locally using ``tox``, execute the following command from the project root: + +.. code-block:: console + + $ uv --directory source run tox run + +Unit tests are run on all supported Python versions (3.11, 3.12, and 3.13). They can be run individually using the following command: + +.. code-block:: console + + $ uv --directory source run tox -e py311 + $ uv --directory source run tox -e py312 + $ uv --directory source run tox -e py313 + +To generate an HTML coverage report, you can add the ``coverage`` environment to the list of environments to run: + +.. code-block:: console + + $ uv --directory source run tox -e py313,coverage + +The linters and static type checkers can also be run individually using the following commands: + +.. code-block:: console + + $ uv --directory source run tox -e pylint + $ uv --directory source run tox -e pycodestyle + $ uv --directory source run tox -e pydoclint + $ uv --directory source run tox -e mypy + +Finally, we use a Markdown linter to ensure the quality of the read me and a spell checker to verify the correct spelling of all text, including code files. Both of these tools are based on `Node.js `_. If you do not have Node.js and NPM installed, this can be easily achieved using the `Node Version Manager (nvm) `_. We recommend installing an `active LTS or maintenance LTS release `_ of Node.js. Once Node.js and NPM are installed, you can install the Markdown linter and the spell checker using the following commands: + +.. code-block:: console + + $ npm --prefix tests/linters/markdownlint install + $ npm --prefix tests/linters/cspell install + +The Markdown linter and the spell checker can be run using the following commands: + +.. code-block:: console + + $ npm --prefix tests/linters/markdownlint run markdownlint + $ npm --prefix tests/linters/cspell run cspell + +If your changes require updates to the configurations of the Markdown linter or the spell checker, please update the following configuration files: + +* **Markdown Linter** -- :repo:`tests/linters/markdownlint/.markdownlint.yaml` +* **Spell Checker** -- :repo:`tests/linters/cspell/.cspell.json` + +Our continuous integration and deployment (CI/CD) pipeline is built using GitHub Actions Workflows. You can use the `act tool `_ to test the GitHub Actions workflow locally. Install the act tool according to the `official installation instructions `_. After the installation, the GitHub Actions workflow can be run locally using the following commands: + +.. code-block:: console + + $ act # Runs all workflows + $ act --job # Runs a single job with the specified ID (e.g., unit-tests, build-documentation, pylint, etc.) + +When prompted to select a Docker image, we recommend using the "full" image. + +If your changes require updates to the GitHub Actions workflows, please update the following configuration files: + +* **Unit Tests, Linting & Building** -- :repo:`.github/workflows/tests.yml` +* **Deployment to PyPI** -- :repo:`.github/workflows/deploy.yml` + +To ensure a successful review of your pull request, please verify that: + +* All linters and static type checkers pass without errors. +* Unit tests succeed for all supported Python versions (3.10 - 3.13). +* The documentation builds successfully. + +If any of these checks fail, we will not be able to accept the pull request. + +6. Update the Changelog +======================= + +As part of your contribution, please ensure that the project's changelog is updated to reflect the modifications you've made. This can be done by editing the :repo:`CHANGELOG.md` file. + +By recording your changes in our changelog, we can maintain a clear and accurate history of updates, making it easier for users and developers to track progress and understand the impact of each release. + +7. Add Yourself to the Contributors List +======================================== + +As a final step before committing your changes and making a pull request, please consider adding your name to our contributors list in :repo:`CONTRIBUTORS.md`. This allows us to formally recognize and appreciate your contribution to the project. + +You may choose to add yourself under a pseudonym or use your actual name; we respect your preference and encourage you to acknowledge your hard work in making this project better. + +8. Commit Your Changes +====================== + +To ensure that your contributions are easily reviewable and maintainable, please strive for a few meaningful, coherent commits with descriptive commit messages. We follow the conventional 50/72 rule: + +* A brief subject line (not exceeding 50 characters) that summarizes the changes. +* A detailed description (with lines capped at 72 characters), separated from the subject line by a blank line. + +Additionally, after each commit, please ensure that the repository remains in a healthy state. If the ``main`` branch has progressed since you branched it off, use a Git rebase instead of a merge to avoid unnecessary merge commits. This helps keep the commit history clean and makes it easier for others to review your changes. + +9. Submit Your Contribution for Review +====================================== + +Once you've completed your development work, push your changes to your forked repository and create a pull request against the main repository. When creating the pull request, please provide a clear and detailed description of your changes, including how they address the specific issue or feature being implemented. + +Be sure to reference the relevant issues in your description so that our review team can easily identify the context for your contribution. We'll strive to review your submission as soon as possible, providing feedback and guidance to ensure a smooth integration process. diff --git a/docs/source/contributors-guide/index.rst b/docs/source/contributors-guide/index.rst new file mode 100644 index 0000000..8289da3 --- /dev/null +++ b/docs/source/contributors-guide/index.rst @@ -0,0 +1,21 @@ +=================== +Contributor's Guide +=================== + +CoRelAy is an open-source project that relies on the contributions of its community, and we are excited to have you join us. Your enthusiasm and support are essential to our success, and we encourage you to participate in various ways. + +There are numerous opportunities to contribute to CoRelAy, including: + +* Filing bug reports or feature requests +* Updating documentation +* Contributing code to the project + +To begin contributing, we recommend starting with a thorough understanding of the project. We suggest reviewing our getting started guide :doc:`../getting-started/index` for an introduction to CoRelAy and its features. + +This section of the documentation will guide you through everything you need to know to file bug reports or feature requests, submit code changes, improve our documentation, and other contributions. We appreciate your interest in helping us improve CoRelAy! + +.. toctree:: + :maxdepth: 2 + + reporting-issues-and-feature-requests + contributing-code-or-documentation diff --git a/docs/source/contributors-guide/reporting-issues-and-feature-requests.rst b/docs/source/contributors-guide/reporting-issues-and-feature-requests.rst new file mode 100644 index 0000000..092f466 --- /dev/null +++ b/docs/source/contributors-guide/reporting-issues-and-feature-requests.rst @@ -0,0 +1,15 @@ +=================================== +Reporting Issues & Feature Requests +=================================== + +If you encounter a bug or have a feature request, we encourage you to submit an issue on our `GitHub page `_. Before creating a new issue, please review existing issues to avoid duplicate efforts and ensure that your report adds value. If you find an open issue related to the same problem, feel free to contribute additional information or context to help us resolve it more efficiently. + +When submitting a new issue, provide as much relevant information as possible to facilitate prompt identification and resolution of the problem. This includes: + +* Operating system details (e.g., Windows 11, macOS 15, Ubuntu 24.04) +* The CoRelAy version you are using +* Any pertinent configuration settings or dependencies + +Also, please provide a clear, step-by-step guide on how to reproduce the issue. This will enable us to diagnose and fix the problem more quickly. If possible, including a minimal example that reproduces the issue (i.e., input data, code example, and analysis results) is highly appreciated. + +You may also file an issue if you have a feature request or enhancement suggestion. Please provide a clear description of the desired feature, its potential benefits, and any relevant use cases. This will help us understand the value of the proposed change and prioritize it accordingly. diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst deleted file mode 100644 index 45d43fd..0000000 --- a/docs/source/getting-started.rst +++ /dev/null @@ -1,312 +0,0 @@ -================ - Getting started -================ - -CoRelAy is a tool to compose small-scale (single-machine) analysis pipelines. It -was created to swiftly implement pipelines to generate analysis data which can -then be visualized using ViRelAy. - -Install -------- - -CoRelAy can be installed directly from PyPI: - -.. code-block:: console - - $ pip install virelay - -To install optional HDBSCAN and UMAP support, use - -.. code-block:: console - - $ pip install corelay[umap,hdbscan] - -For the current development version, or to try out examples, clone and install -with: - -.. code-block:: console - - $ git clone https://github.com/virelay/virelay.git - $ pip install ./virelay - -Basic Usage ------------ - -The main objective of ViRelAy is the quick and hassle-free composition of -analysis **Pipelines**. **Pipelines** are designed with a number of steps, -called **Task**, which each have their own default function, defined using a -**Processor**. Any step of the pipeline may be individually changed by assigning -a new **Processor**. **Processors** have **Params** which configure their -functional hyperparameters. - - -Processor and Param -^^^^^^^^^^^^^^^^^^^ - -A **Processor** can be defined in the following way: - -.. code-block:: python - - from types import FunctionType - from typing import Annotated - - from corelay.processor.base import Processor, Param - - - class MyProcess(Processor): - # Parameters are registered by defining a class attribute of type Param, - # and will be set in __init__ automatically, which expects keyword - # arguments with the same name the first value is a type specification, - # the second a default value - stuff: Annotated[int, Param(dtype=int, default=2)] - - # as class methods have to be bound explicitly, func here acts like a - # static function of MyProcess. For more information see - # corelay.processor.base.FunctionProcessor - func: Annotated[FunctionType, Param(FunctionType, lambda x: x**2)] - - # Parameters can be accessed as self. - def function(self, data: Any) -> Any: - return self.stuff * self.func(data) + 3 - -Every **Processor** is a sub-class of -:py:class:`~corelay.processor.base.Processor`, and must implement -:py:meth:`~corelay.processor.base.Processor.function`, which typically only uses -a single positional argument. -Parameters for the Processor can be specified by assigning an instance of -:py:class:`~corelay.processor.base.Param` as a class attribute. -The name of the attribute can be used as a keyword argument to specify the value -when creating an instance of the **Processor**, and accessed under the same name. -Each **Param** has a datatype ``dtype`` and a default value ``default``. -**Processor** instances can be used like functions, or assigned to a **Task** of -a **Pipeline**. - -Pipeline and Task -^^^^^^^^^^^^^^^^^ - -**Pipelines** consist of multiple, sequential, pre-determined steps, called -**Tasks**, and can be defined in the following way: - -.. code-block:: python - - from corelay.pipeline.base import Pipeline, Task - from corelay.processor.base import FunctionProcessor - from corelay.processor.affinity import Affinity, RadialBasisFunction - from corelay.processor.distance import Distance, SciPyPDist - - - class MyPipeline(Pipeline): - # Task are registered in order by creating a class attribute of type - # Task() and, like params, are expected to be supplied with the same name - # in __init__ as a keyword argument. The first value is an optional - # expected Process type, second is a default value, which has to be an - # instance of that type. If the default argument is not a Process, it will - # be converted to a FunctionProcessor by default, functions fed to - # FunctionProcessors are by default not bound to the class. To bind them, - # we can supply `bind_method=True` to the FunctionProcessor. Supplying it - # to the task changes the default value of the Processor before creation: - prepreprocess = Task( - proc_type=FunctionProcessor, - default=lambda self, x: x * 2, - bind_method=True - ) - # Otherwise, we do not need to supply `self` for the default function: - preprocess = Task(proc_type=FunctionProcessor, default=lambda x: x**2) - pdistance = Task(Distance, SciPyPDist(metric='sqeuclidean')) - affinity = Task(Affinity, RadialBasisFunction(sigma=1.0)) - # empty task, does nothing (except return input) by default - postprocess = Task() - -Every **Pipeline** is a sub-class of :py:class:`~corelay.pipeline.base.Pipeline`. -**Tasks** of a pipeline are created by assigning an instance of -:py:class:`~corelay.pipeline.base.Task` as a class attribute, similar to -**Params** in **Processors**. -Each **Task** has, each optional, a **Processor**-type ``proc_type``, a default -**Processor** for the Task ``default``. Additional keyword arguments can be -specified as default parameter values that should be assigned to any -**Processor** that is used for the **Task**. The keyword argument -``bind_method`` is specific to :py:class:`~corelay.processor.base.FunctionProcessor`, -and describes, whether the function is static (default, ``bind_method=False``), -or whether it should have access to the **Processor** instance. -Functions can be passed instead of **Processors**, which will be implicitly -converted to a :py:class:`~corelay.processor.base.FunctionProcessor`. -**Tasks** can be assigned by passing **Processors** with their respective -keyword argument during instantiation of the **Pipeline**, or by directly -assigning them to the respective attribute. - -**Pipelines** and **Processors** can be instantiated and used in the following -way: - -.. code-block:: python - - import numpy - - from corelay.processor.base import FunctionProcessor - from corelay.processor.affinity import RadialBasisFunction - from types import FunctionType - - # Use Pipeline 'as is' - pipeline = MyPipeline() - output1 = pipeline(numpy.random.rand(5, 3)) - print('Pipeline output:', output1) - - # Tasks are filled with Processes during initialization of the Pipeline - # class keyword arguments do not have to be in order, and if not supplied, - # the default value will be used - custom_pipeline = MyPipeline( - # The pipeline's Task sets the `bind_method` Parameter's default to - # True. Supplying a value here avoids falling back to the default - # value, and thus we do not need a `self` argument for our function: - prepreprocess=FunctionProcessor( - processing_function=lambda x: x + 1, bind_method=False - ), - preprocess=lambda x: x.mean(1), - postprocess = MyProcess(stuff=3) - ) - custom_pipeline.affinity = RadialBasisFunction(sigma=0.1), - output2 = custom_pipeline(numpy.ones((5, 3, 5))) - print('Custom pipeline output:', output2) - -Like **Processors**, executing a **Pipeline** can be done by simply calling it -like a function. - -Examples --------- - -More examples to highlight some features of **CoRelAy** can be found in -:repo:`example/`. The following demonstrates, how to create a functional -pipeline based on :py:class:`corelay.pipeline.spectral.SpectralClustering`. A -similar version of the following code may be found in -:repo:`example/memoize_spectral_pipeline.py`. - -.. code-block:: python - - """An example script, which uses memoization to store (intermediate) results.""" - - import time - from collections.abc import Sequence - from typing import Annotated, Any, SupportsIndex - - import h5py - import numpy - from numpy.typing import NDArray - - from corelay.base import Param - from corelay.io.storage import HashedHDF5 - from corelay.pipeline.spectral import SpectralClustering - from corelay.processor.base import Processor - from corelay.processor.clustering import KMeans - from corelay.processor.embedding import TSNEEmbedding, EigenDecomposition - from corelay.processor.flow import Sequential, Parallel - - - class Flatten(Processor): - """Represents a CoRelAy processor, which flattens its input data.""" - - def function(self, data: Any) -> Any: - """Applies the flattening to the input data. - - Args: - data (Any): The input data that is to be flattened. - - Returns: - Any: Returns the flattened data. - """ - - input_data: NDArray[Any] = data - input_data.sum() - return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) - - - class SumChannel(Processor): - """Represents a CoRelAy processor, which sums its input data across channels, i.e., its second axis.""" - - def function(self, data: Any) -> Any: - """Applies the summation over the channels to the input data. - - Args: - data (Any): The input data that is to be summed over its channels. - - Returns: - Any: Returns the data that was summed up over its channels. - """ - - input_data: NDArray[Any] = data - return input_data.sum(axis=1) - - - class Normalize(Processor): - """Represents a CoRelAy processor, which normalizes its input data.""" - - axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] - """A parameter of the processor, which determines the axis over which the data is to be normalized. Defaults to the second and third axes.""" - - def function(self, data: Any) -> Any: - """Normalizes the specified input data. - - Args: - data (Any): The input data that is to be normalized. - - Returns: - Any: Returns the normalized input data. - """ - - input_data: NDArray[Any] = data - return input_data / input_data.sum(self.axes, keepdims=True) - - - def main() -> None: - """The entrypoint to the memoize_spectral_pipeline script.""" - - # Fixes the random seed for reproducibility - numpy.random.seed(0xDEADBEEF) - - # Opens an HDF5 file in append mode for the storing the results of the analysis and the memoization of intermediate pipeline results - with h5py.File('test.analysis.h5', 'a') as analysis_file: - - # Creates a HashedHDF5 IO object, which is an IO object that stores outputs of processors based on hashes in an HDF5 file - io_object = HashedHDF5(analysis_file.require_group('proc_data')) - - # Generates some exemplary data - data = numpy.random.normal(size=(64, 3, 32, 32)) - number_of_clusters = range(2, 20) - - # Creates a SpectralClustering pipeline, which is one of the pre-defined built-in pipelines - pipeline = SpectralClustering( - - # Processors, such as EigenDecomposition, can be assigned to pre-defined tasks - embedding=EigenDecomposition(n_eigval=8, io=io_object), - - # Flow-based processors, such as Parallel, can combine multiple processors; broadcast=True copies the input as many times as there are - # processors; broadcast=False instead attempts to match each input to a processor - clustering=Parallel([ - Parallel([ - KMeans(n_clusters=k, io=io_object) for k in number_of_clusters - ], broadcast=True), - - # IO objects will be used during computation when supplied to processors, if a corresponding output value (here identified by hashes) - # already exists, the value is not computed again but instead loaded from the IO object - TSNEEmbedding(io=io_object) - ], broadcast=True, is_output=True) - ) - - # Processors (and Params) can be updated by simply assigning corresponding attributes - pipeline.preprocessing = Sequential([ - SumChannel(), - Normalize(), - Flatten() - ]) - - # Processors flagged with "is_output=True" will be accumulated in the output; the output will be a tree of tuples, with the same hierarchy as - # the pipeline (i.e., _clusterings here contains a tuple of the k-means outputs) - start_time = time.perf_counter() - _clusterings, _tsne = pipeline(data) - - # Since we memoize our results in an HDF5 file, subsequent calls will not compute the values (for the same inputs), but rather load them from - # the HDF5 file; try running the script multiple times - duration = time.perf_counter() - start_time - print(f'Pipeline execution time: {duration:.4f} seconds') - - - if __name__ == '__main__': - main() diff --git a/docs/source/getting-started/basic-usage.rst b/docs/source/getting-started/basic-usage.rst new file mode 100644 index 0000000..9cf4782 --- /dev/null +++ b/docs/source/getting-started/basic-usage.rst @@ -0,0 +1,178 @@ +=========== +Basic Usage +=========== + +The primary goal of CoRelAy is to facilitate the efficient and streamlined creation of data analysis pipelines. At its core, a **pipeline** (:py:class:`~corelay.pipeline.base.Pipeline`) consists of multiple, modular components called **tasks** (:py:class:`~corelay.pipeline.base.Task`), which are executed in sequence to achieve the desired processing outcome. + +Each task's operation is defined by an associated **processor** (:py:class:`~corelay.processor.base.Processor`), which encapsulates the specific processing logic required for that step. Tasks provide default functionality that can be easily customized by replacing their corresponding processors with alternative implementations. + +Processors in CoRelAy are highly configurable entities, allowing users to tailor their behavior using **parameters** (:py:class:`~corelay.base.Param`), which dictate the specific processing actions taken by each processor. + +The following sections provide an overview of these key concepts and demonstrate how to leverage them when working with CoRelAy. + +Processors & Parameters +======================= + +In CoRelAy, a **Processor** is defined by sub-classing :py:class:`~corelay.processor.base.Processor`. The functionality of a processor is defined by overriding the :py:meth:`Processor.function ` method, which is called with a single positional argument, the data to be processed, and returns the processed data. + +**Parameters** can be registered with the processor, by defining a class attribute of type :py:class:`~typing.Annotated`, where the first argument is the data type of the parameter, and the second is an instance of :py:class:`~corelay.base.Param`. The first argument to :py:meth:`Param.__init__ ` is the runtime data type of the parameter (which may be different from the type hint used as the first argument to :py:class:`~typing.Annotated`, e.g., the type hint may be a generic type like ``dict[int]``, while the runtime type must be a concrete type like :py:class:`dict`, i.e., the same type that would be returned by :py:class:`type`). The second argument is the default value of the parameter. + +.. note:: + + If you come from a version of CoRelAy before 0.3.0, you may be used to the old syntax of registering parameters by assigning an instance of :py:class:`~corelay.base.Param` to a class attribute. For more information on this change and how to migrate, please refer to the :doc:`migration guide <../migration-guide/migrating-from-v0.2-to-v0.3>`. + +The processor will automatically track the parameters and allows users to set them in the constructor using the attribute's name as a keyword argument. Parameters can, however, also be made into positional arguments by setting the ``is_positional`` argument of :py:meth:`Param.__init__ ` to :py:obj:`True`. This allows for a more flexible and user-friendly interface when creating custom processors. The parameters can be accessed as attributes of the processor instance. Invoking a processor to perform the associated operation is as easy as calling it like a function. + +The following example demonstrates how to create a custom processor by subclassing :py:class:`~corelay.processor.base.Processor` and how to define parameters using :py:class:`~corelay.base.Param`: + +.. code-block:: python + + from types import FunctionType + from typing import Annotated, Any + + import numpy + + from corelay.base import Param + from corelay.processor.base import Processor + + + class MyProcessor(Processor): + """A custom CoRelAy processor, which applies a configurable function to its input data and multiplies it by a configurable value.""" + + multiplier: Annotated[int, Param(dtype=int, default=2)] + """An :py:class:`int` parameter, which is multiplied with the result of the function.""" + + function_to_apply: Annotated[FunctionType, Param(FunctionType, lambda x: x**2)] + """A function, which is applied to the input data.""" + + def function(self, data: Any) -> Any: + """Applies the custom function :py:attr:`function_to_apply` to the input data and multiplies it by the parameter :py:attr:`multiplier`. + + Args: + data (Any): The input data that is to be processed. + + Returns: + Any: Returns the processed data. + """ + + # Parameters can be accessed as self. + return self.multiplier * self.function_to_apply(data) + +.. note:: + + Please note, that in the above example, the type of the ``function_to_apply`` parameter is :py:class:`~types.FunctionType`. Unfortunately, Python does not have a unified type for functions. Instead, functions, lambda functions, methods, built-in functions, built-in methods, and other functions like NumPy array and universal functions are all represented by different types. CoRelAy is smart enough to recognize this and will allow you to pass any kind of function or method to a parameter of type :py:class:`~types.FunctionType`. + +Pipelines & Tasks +================= + +**Pipelines** represent entire data processing workflows. They consist of multiple, sequential, pre-determined steps, called **tasks**. Every pipeline is a sub-class of :py:class:`~corelay.pipeline.base.Pipeline`. Tasks are registered by creating a class attribute of type :py:class:`~typing.Annotated`, with the first argument being the type of the processor that is expected to be used in the task, and the second being an instance of :py:class:`~corelay.pipeline.base.Task`. The first argument to :py:meth:`Task.__init__ ` is the type of the processor that is expected to be used in the task, and the second argument is the default processor that is used by the task, if the user does not specify a custom processor. Like parameters, the processors of the tasks can be supplied to the :py:meth:`Pipeline.__init__ ` method as keyword arguments with the same name as the corresponding attribute. All additional keyword arguments that are passed to the :py:class:`~corelay.pipeline.base.Task` are assigned to the parameters of the processor. Like processors, pipelines can be executed by simply calling it like a function. + +The following example demonstrates how to create a custom pipeline by subclassing :py:class:`~corelay.pipeline.base.Pipeline` and how to define tasks using :py:class:`~corelay.pipeline.base.Task`: + +.. code-block:: python + + from typing import Annotated, Any + + from corelay.pipeline.base import Pipeline, Task + from corelay.processor.base import FunctionProcessor, Processor + from corelay.processor.affinity import Affinity, RadialBasisFunction + from corelay.processor.distance import Distance, SciPyPDist + + + class MyPipeline(Pipeline): + """A custom CoRelAy pipeline, which applies a series of processors to its input data.""" + + pre_pre_process: Annotated[FunctionProcessor, Task(proc_type=FunctionProcessor, default=lambda self, x: x * 2, bind_method=True)] + """A pre-pre-processing task, which applies a function to the input data. By default, the input data is multiplied by 2.""" + + pre_process: Annotated[FunctionProcessor, Task(proc_type=FunctionProcessor, default=lambda x: x**2)] + """A pre-processing task, which applies a function to the input data. By default, the input data is squared.""" + + pairwise_distance: Annotated[Distance, Task(Distance, SciPyPDist(metric='sqeuclidean'))] + """A task, which applies a pairwise distance function to the input data. By default, the squared euclidean distance is used. The + :py:class:`~corelay.processor.distance.Distance` class is a base class for all distance processors. + """ + + affinity: Annotated[Affinity, Task(Affinity, RadialBasisFunction(sigma=1.0))] + """A task, which applies an affinity function to the input data. By default, the radial basis function is used. The + :py:class:`~corelay.processor.distance.Affinity` class is a base class for all affinity processors. + """ + + post_process: Annotated[Processor, Task()] + """A post-processing task, which does nothing by default and returns the input data as is.""" + +The :py:class:`~corelay.processor.base.FunctionProcessor` class is a :py:class:`~corelay.processor.base.Processor` that applies a customizable function to the input data. In essence it can be used to turn any Python function into a processor. If the value or default value of a task is a function, it will be automatically converted to a :py:class:`~corelay.processor.base.FunctionProcessor` (this is irrespective of the task's processor type; if the type is neither :py:class:`~corelay.processor.base.Processor` nor :py:class:`~corelay.processor.base.FunctionProcessor`, the task would still convert a function to a :py:class:`~corelay.processor.base.FunctionProcessor`, which will lead to an error as the task verifies that the processor type and the processor/default processor are consistent). This is why we can also just supply a lambda expression as the default value of the task. + +By default, functions fed to :py:class:`~corelay.processor.base.FunctionProcessor` are not bound to the class. To bind them, we can supply `bind_method=True` to the :py:class:`~corelay.processor.base.FunctionProcessor`. Please note how the ``bind_method`` parameter of :py:class:`~corelay.processor.base.FunctionProcessor` is omitted in the ``pre_process`` task and therefore defaults to :py:obj:`False`. This means that the function is not bound to the class and does not have access to ``self``. + +Pipelines and processors can be instantiated and used in the following way: + +.. code-block:: python + + import numpy + + from corelay.processor.base import FunctionProcessor + from corelay.processor.affinity import RadialBasisFunction + + # Creates a new pipeline without specifying any parameters, which means that the default values of the tasks will be used + pipeline = MyPipeline() + first_output = pipeline(numpy.random.rand(5, 3)) + print('Pipeline output:', first_output) + + # Tasks are filled with processors during initialization of the Pipeline class; keyword arguments do not have to be in order, and if not supplied, + # the default value will be used + custom_pipeline = MyPipeline( + + # By setting the bind_method parameter to False, the function is not bound to the class and we do not need to a self argument + pre_pre_process=FunctionProcessor(processing_function=lambda x: x + 1, bind_method=False), + + # The pre_process task is set to a custom function, which is not of type Distance and is therefore automatically converted to a + # FunctionProcessor + pre_process=lambda x: x.mean(1), + + # The pairwise_distance task is omitted and therefore defaults to the squared euclidean distance; the affinity task is set to a + # RadialBasisFunction with a lower sigma value + affinity=RadialBasisFunction(sigma=0.1), + + # The empty post_process task is set to an instance of our custom processor MyProcessor and the multiplier parameter is set to 3 + post_process=MyProcessor(multiplier=3) + ) + second_output = custom_pipeline(numpy.ones((5, 3, 5))) + print('Custom pipeline output:', second_output) + +Memoization +=========== + +CoRelAy provides a built-in memoization mechanism that allows you to cache the results of expensive computations and reuse them when the same inputs are encountered again. This can significantly speed up your data processing pipelines, especially when dealing with large datasets or complex calculations. When adding a storage container to a pipeline, intermediate results are automatically cached and will be reused both during the pipeline execution and when the pipeline is called again with the same input data and parameters, as the intermediate results are stored on disk. To enable memoization, you need to add a storage container to your pipeline. The following example demonstrates how to do this: + +.. code-block:: python + + import time + + import h5py + import numpy + + from corelay.io.storage import HashedHDF5 + + # Opens an HDF5 file in append mode for the storing the results of the analysis and the memoization of intermediate pipeline results + with h5py.File('test.analysis.h5', 'a') as analysis_file: + + # Creates a HashedHDF5 IO object, which is storage container that stores outputs of processors based on hashes in an HDF5 file + io_object = HashedHDF5(analysis_file.require_group('proc_data')) + + # Creates a new pipeline with the storage container as the IO object + pipeline = MyPipeline(io=io_object) + + # Runs the pipeline and measures the execution time + start_time = time.perf_counter() + output = pipeline(numpy.ones((1000, 1000))) + duration = time.perf_counter() - start_time + + # Since we memoize our results in an HDF5 file, subsequent calls will not compute the values (for the same inputs), but rather load them + # from the HDF5 file; try running the script multiple times + print(f'Pipeline output: {output}') + print(f'Pipeline execution time: {duration:.4f} seconds') + +Running the example should yield faster execution times on subsequent runs, as the intermediate results are cached in the HDF5 file. The first run will take longer, as the pipeline has to compute all intermediate results and store them in the HDF5 file. Subsequent runs will load the intermediate results from the HDF5 file, which is much faster. The difference in execution time in this example is, of course, miniscule, as the pipeline is very simple and the data is small. However, in real-world applications, the difference can be significant. + +Fleshed out versions of the above examples and more examples to highlight the features of CoRelAy can be found in :repo:`docs/examples/`. diff --git a/docs/source/getting-started/example-project.rst b/docs/source/getting-started/example-project.rst new file mode 100644 index 0000000..5d9853e --- /dev/null +++ b/docs/source/getting-started/example-project.rst @@ -0,0 +1,326 @@ +=============== +Example Project +=============== + +In this section, we will build a more elaborate example project, which demonstrates one of the main usage scenarios of CoRelAy: the creation of a Spectral Relevance Analysis (SpRAy) :cite:p:`lapuschkin2019spray` pipeline with the goal of analyzing attributions of model predictions generated using tools like `Zennit `_ and visualizing the results in `ViRelAy `_. + +Introduction +============ + +To gain insights into how machine learning models make predictions, explainable artificial intelligence (XAI) methods are used to provide transparency into their decision-making processes. There are two types: Local XAI provides insights into individual predictions, while Global XAI offers a general understanding of a machine learning model. However, both types have limitations and may suffer from human bias. Local XAI is time-consuming when analyzing large datasets, while Global XAI can only measure expected features or effects. SpRAy is a technique that bridges the gap by automating the analysis of local explanations for large datasets. + +Zennit is a Python framework for generating local explanations of machine learning models using PyTorch. It provides a unified interface for various XAI methods, such as saliency maps, SmoothGrad, and Deep Taylor Decomposition, but it has a strong focus on Layer-wise Relevance Propagation (LRP) :cite:p:`bach2015pixel,montavon2019layer`. Zennit can be used to generate the attributions for model predictions, that are then analyzed in a SpRAy pipeline using CoRelAy. For more information about Zennit, please refer to the `Zennit documentation `_. + +ViRelAy is an XAI visualization tool, that can be used for the visual inspection and analysis of the results of SpRAy pipelines. It runs a small web application to view, explore, and interact with the source data, its attributions, clusterings, and embeddings. ViRelAy is designed to simplify the analysis of classifiers and their underlying datasets. If you want to find out more about ViRelAy, please refer to the `ViRelAy documentation `_. + +The SpRAy workflow involves four key steps: + +1. **Model Inference** -- The model is run on all relevant training samples, generating predictions for each. +2. **Explain Model Decisions** -- Suitable LRP variants are used to generate attributions and heatmaps for each sample and class in Zennit, providing insights into the model's decision-making process. +3. **Spectral Analysis** -- An eigenvalue-based cluster analysis identifies different prediction strategies within the data using CoRelAy. +4. **Visualization & Analysis** -- The resulting embeddings and clusterings are visualized in ViRelAy to identify anomalous behavior or potential biases in the model, enabling improvements to be made. + +.. figure:: ../images/spray-workflow.png + :alt: The SpRAy workflow. + :align: center + + Figure 1: The SpRAy workflow including the generation of attributions using Zennit, the clustering of the attributions using CoRelAy, and the visualization of the results using ViRelAy. + +Loading the Data +================ + +In this example project, we will assume that the first two steps, model inference and explaining the model decisions, have already taken place and we will focus on the third step, the spectral analysis of the attributions generated by Zennit. For more information on how to use Zennit to generate attributions, please refer to the `tutorial `_ in the Zennit documentation. Also, please refer to the `example project `_, as well as the `database specification `_ in the ViRelAy documentation to learn how to generate the correct database structure for the SpRAy pipeline. As a shortcut, this documentation also includes an :repo:`example script ` that randomly generates a dataset, attributions, and spectral analyses database, which can be used to test the SpRAy pipeline and visualize the results in ViRelAy. + +To complete this example project, you will need an attribution database, which is an HDF5 file containing the attributions generated by Zennit. We cannot go into detail on how attribution databases are structured, but at a very basic level, the database contains an HDF5 dataset called ``attribution`` of shape `(number_of_samples, number_of_channels, height, width)`, which contains the attribution maps for all analyzed dataset samples, and an HDF5 dataset called ``label`` of shape `(number_of_samples,)`, which contains the labels of the samples. Additionally, a label map file is required, which is a JSON file that maps the label indices to their corresponding class names and optionally to WordNet synset IDs. For example, the following listing shows an excerpt from a label map file for the CIFAR-10 dataset: + +.. code-block:: json + + [ + { + "index": 0, + "word_net_id": "n02691156", + "name": "Airplane" + }, + { + "index": 1, + "word_net_id": "n02958343", + "name": "Automobile" + }, + { + "index": 2, + "word_net_id": "n01503061", + "name": "Bird" + }, + + // [...] + ] + +Before we can start with the data analysis, the label map file and the attribution database need to be loaded. The following code snippet shows how to load the label map file and convert the information to a list of class indices, and two dictionaries that map the label indices to their corresponding class names and WordNet synset IDs, respectively. The label map file is assumed to be in the same directory as the script, but it can be placed anywhere as long as the path is correctly specified. + +.. code-block:: python + + import json + + with open('label-map.json', 'r', encoding='utf-8') as label_map_file: + label_map = json.load(label_map_file) + class_indices = [label['index'] for label in label_map] + wordnet_id_map = {label['index']: label['word_net_id'] for label in label_map} + class_name_map = {label['index']: label['name'] for label in label_map} + +Now, we can go on to load the attribution database. The spectral analysis is usually performed on a per-class basis, meaning that the attributions are grouped by their class labels. This is done because the goal of SpRAy is usually to analyze the classification strategies a model has learned for each class. It may, however, also be useful to, for example, group them by the prediction of the classifier, to analyze why a classifier has misclassified certain samples. The attributions can be in any order in the attribution dataset and do not necessarily have to come in the order of the labels of the dataset samples they were generated from. For this reason, we will first load the labels of the attributions, which are stored in the ``label`` HDF5 dataset. This can then be used to load the attributions for each class label independently. + +.. code-block:: python + + import h5py + import numpy + + with h5py.File('attributions.hdf5', 'r') as attributions_file: + labels = attributions_file['label'][:] + + for class_index in class_indices: + indices_of_samples_in_class, = numpy.nonzero(labels == class_index) + attribution_data = attributions_file['attribution'][indices_of_samples_in_class, :] + + # Perform spectral analysis on the attribution_data for the current class_index + +Spectral Cluster Analysis +========================= + +Spectral cluster analysis is a clustering technique that is a based on similarity graphs. First a similarity graph, such as a *k*-nearest neighbor graph, is constructed using a distance metric, such as the euclidean distance. Then the Laplacian of the adjacency matrix of the graph is computed, which is a matrix representation of the similarity graph. The eigendecomposition of the Laplacian yields the eigenvectors and eigenvalues, which can then be used to assign cluster labels to the data points using any arbitrary clustering algorithm. In SpRAy, the clusterings can then optionally be embedded using t-SNE or UMAP to embed the results in a lower-dimensional space, which can then be visualized in ViRelAy. For a deeper understanding of spectral clustering, please refer to :cite:p:`vonLuxburg2007tutorial`. + +CoRelAy already provides a pre-implemented SpRAy pipeline, which can be used to analyze the attributions generated by Zennit. The pipeline is implemented in the :py:class:`~corelay.pipeline.spectral.SpectralClustering` class. The pipeline comprises the following 7 tasks: + +1. :py:attr:`~corelay.pipeline.spectral.SpectralEmbedding.preprocessing` -- A pre-processing task, which can be used to apply any custom function to the data before the spectral analysis, e.g., rescaling the annotations or folding multi-channel relevances into a single channel. By default, it applies a no-op function, which does not change the data. +2. :py:attr:`~corelay.pipeline.spectral.SpectralEmbedding.pairwise_distance` -- A task, which computes the pairwise distance matrix of the input data. By default, it uses the euclidean distance metric, but it can be customized to use any other distance metric. +3. :py:attr:`~corelay.pipeline.spectral.SpectralEmbedding.affinity` -- An affinity task, which computes the affinity matrix from the pairwise distance matrix. By default, it uses a sparse *k*-nearest neighbors graph with 10 neighbors, but it can be customized to use any other affinity algorithm. +4. :py:attr:`~corelay.pipeline.spectral.SpectralEmbedding.laplacian` -- The graph laplacian function that is applied to the affinity matrix. The task defaults to a symmetric normal laplacian, but it can be changed to another Laplacian, like the random walk normal Laplacian function. +5. :py:attr:`~corelay.pipeline.spectral.SpectralEmbedding.embedding` -- The embedding function to be applied to the graph laplacian. Defaults to an eigendecomposition with 32 eigenvalues. +6. :py:attr:`~corelay.pipeline.spectral.SpectralClustering.select_eigenvector` -- This task is used to select the eigenvectors from the output of the spectral embedding. Since the spectral embedding task returns a tuple containing the eigenvalues first and eigenvectors second, it defaults to the second output of the spectral embedding, but if a custom embedding function is used, it can be customized to select the appropriate output. +7. :py:attr:`~corelay.pipeline.spectral.SpectralClustering.clustering` -- A clustering algorithm that is to be applied to the spectral embedding. It defaults to *k*-Means clustering with 2 clusters, but it can be customized to use any other clustering algorithm, such as DBSCAN, HDBSCAN, or agglomerative clustering. This task can also be used to embed the results of the embedding task using t-SNE or UMAP to a a lower dimensional space, which can then be visualized in ViRelAy. + +.. note:: + + Please note, that you have to install CoRelAy with HDBSCAN and UMAP support if you want to use HDBSCAN as a clustering algorithm or UMAP as a dimensionality reduction algorithm. This can be done by installing CoRelAy with the ``[hdbscan,umap]`` extra, e.g., using the following command: + + .. code-block:: shell + + pip install corelay[hdbscan,umap] + +So, to perform the spectral analysis of the attributions, we can simply create an instance of the :py:class:`~corelay.pipeline.spectral.SpectralClustering` class and run it on the attribution data for each class. The following code snippet shows how to do this: + +.. code-block:: python + + from typing import Any + + import h5py + import numpy + + from corelay.pipeline.spectral import SpectralClustering + + pipeline = SpectralClustering() + + with h5py.File('attributions.hdf5', 'r') as attributions_file: + labels = attributions_file['label'][:] + + for class_index in class_indices: + indices_of_samples_in_class, = numpy.nonzero(labels == class_index) + attribution_data = attributions_file['attribution'][indices_of_samples_in_class, :] + + (eigenvalues, eigenvectors), spectral_clustering = pipeline(attribution_data) + +Depending on the input data, it may, however, be necessary to pre-process the data before running the spectral analysis. For example, the data may be normalized and the array flattened to remove the channel, height, and width dimensions. This is a great opportunity to create some custom processors that can be used to pre-process the data before the spectral analysis. The following code snippet shows a processor that normalizes the data and a processor that flattens the data: + +.. code-block:: python + + from collections.abc import Sequence + from typing import Annotated, Any, SupportsIndex + + import numpy + + from corelay.base import Param + from corelay.processor.base import Processor + + + class Flatten(Processor): + def function(self, data: Any) -> Any: + input_data: numpy.ndarray[Any, Any] = data + return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) + + + class Normalize(Processor): + axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] + + def function(self, data: Any) -> Any: + input_data: numpy.ndarray[Any, Any] = data + return input_data / input_data.sum(self.axes, keepdims=True) + +Now, we can use these processors in the spectral clustering pipeline to customize the pre-processing of the data. We will also customize the clustering algorithm used to cluster the spectral embedding: Since it is often not known in advance how many clusters are present in the data, we will compute multiple *k*-Means clusterings with different numbers of clusters. These can all be visualized in ViRelAy, which allows us to explore the data and find the optimal number of clusters. Also, the spectral embeddings are usually high-dimensional, which renders a visualization of all dimensions at the same time impossible. For this reason, we will use dimensionality reduction techniques to embed the spectral embeddings in a 2-dimensional space. Depending on the input data, sometimes t-SNE is better for finding defects in the classifier and/or dataset, while other times UMAP can be more informative. Therefore, we will compute both t-SNE and UMAP embeddings of the spectral embeddings. The following code snippet shows how to set up the spectral clustering pipeline with the custom processors and the customized clustering algorithms: + +.. code-block:: python + + from corelay.pipeline.spectral import SpectralClustering + from corelay.processor.clustering import KMeans + from corelay.processor.embedding import TSNEEmbedding, UMAPEmbedding + from corelay.processor.flow import Sequential, Parallel + + pipeline = SpectralClustering( + preprocessing=Sequential([ + Normalize(axes=(1, 2, 3)), + Flatten() + ]), + clustering=Parallel([ + Parallel([KMeans(n_clusters=number_of_clusters) for number_of_clusters in [2, 3, 4, 5]], broadcast=True), + UMAPEmbedding(), + TSNEEmbedding() + ], broadcast=True, is_output=True) + ) + + (eigenvalues, eigenvectors), (kmeans, umap, tsne) = pipeline(attribution_data) + +The :py:class:`~corelay.processor.flow.Sequential` and :py:class:`~corelay.processor.flow.Parallel` processors are used to direct the flow of data through the pipeline. They are sequential and parallel in the sense of data, not in the sense of execution. This means that :py:class:`~corelay.processor.flow.Sequential` will feed its input data to the first processor in the sequence, and the output of that processor will be fed to the next processor, and so on. The :py:class:`~corelay.processor.flow.Parallel` processor will also execute the processors in the sequence sequentially, but it will feed the input data to all processors in the sequence. Depending on whether the :py:attr:`~corelay.processor.flow.Parallel.broadcast` parameter is set to :py:obj:`True` or :py:obj:`False`, the :py:class:`~corelay.processor.flow.Parallel` processor will either treat its input as an array of inputs to its sub-processors, or it will broadcast its input to all sub-processors, i.e., the :py:class:`~corelay.processor.clustering.KMeans`, :py:class:`~corelay.processor.embedding.UMAPEmbedding`, and :py:class:`~corelay.processor.embedding.TSNEEmbedding` processors assigned to the :py:attr:`~corelay.pipeline.spectral.SpectralClustering.clustering` task in the pipeline will all receive the same array of eigenvectors as input, that were selected by the :py:attr:`~corelay.pipeline.spectral.SpectralClustering.select_eigenvector` task. + +The :py:attr:`~corelay.processor.base.Processor.is_output` parameter is used to indicate whether the output of a processor should be included in the output of the pipeline. A pipeline will concatenate the outputs of all processors that have the :py:attr:`~corelay.processor.base.Processor.is_output` parameter set to :py:obj:`True` into a tuple an return it. Please note that processors in turn may also return tuples, which will result in a nested tuple structure. For example, the :py:class:`~corelay.processor.flow.Sequential` and :py:class:`~corelay.processor.flow.Parallel` processors will return a tuple containing the outputs of all of their sub-processors. The :py:class:`~corelay.pipeline.spectral.SpectralClustering` pipeline has set the default processors of the :py:attr:`~corelay.pipeline.spectral.SpectralEmbedding.embedding` and :py:attr:`~corelay.pipeline.spectral.SpectralClustering.clustering` tasks to have the :py:attr:`~corelay.processor.base.Processor.is_output` parameter set to :py:obj:`True`. + +Generating the Analysis Database +================================ + +Now, that the spectral analysis has been performed, the results can be stored in a database. Again, we will not go into detail on how the database is structured. For more information, please refer to the `database specification `_ in the ViRelAy documentation. For now, it suffices to know, that the database is an HDF5 file, which contains HDF5 groups for each analysis that was performed. The names of the analysis groups can be chosen arbitrarily, but it is common to use the class name of the class the analysis was performed on. Each analysis group contains an HDF5 dataset called ``index``, which contains the indices of the dataset samples whose corresponding attributions were analyzed. Furthermore, analysis groups contain two further HDF5 sub-groups: The ``embedding`` group, which contains the embeddings produced by the analysis, and the ``clustering`` group, which contains the clusterings produced by the analysis. + +Each entry in the embedding group is an HDF5 dataset that contains the embedding data. The name of the dataset can be chosen arbitrarily, but it is common to use the name of the embedding method, such as ``tsne``, or the name of the embedding method and a relevant parameter of the embedding algorithm, such as the number of components used in the t-SNE embedding, e.g., ``tsne-2``. It can have additional attributes to further describe the embedding data: The ``eigenvalue`` attribute is a dataset that contains the eigenvalues of the spectral embedding and the ``embedding`` attribute is a string that contains the name of the embedding on which this embedding was based (e.g., the t-SNE embedding in this project is based on the spectral embedding). + +Although, the clustering group differs in the details of its make up, it has the same basic structure as the embedding group. It contains HDF5 datasets for each clustering that was performed, which are named after the clustering algorithm and the number of clusters used in the clustering, e.g., ``kmeans-2`` for a *k*-Means clustering with 2 clusters. The datasets can have additional attributes to further describe the clustering data: The ``embedding`` attribute is a string that contains the name of the embedding on which this clustering was based (e.g., the *k*-Means clustering in this project is based on the spectral embedding). + +The following code listing shows how to generate the analysis database from the results of the spectral analysis pipeline: + +.. code-block:: python + + import h5py + import numpy + + (eigenvalues, eigenvectors), (kmeans, (umap, tsne)) = pipeline(attribution_data) + + with h5py.File('analysis.hdf5', 'a') as analysis_file: + + # The name of the analysis is the name of the class + analysis_name = wordnet_id_map.get(class_index, f'{class_index:08d}') + + # Adds the indices of the samples in the current class to the analysis database + analysis_group = analysis_file.require_group(analysis_name) + analysis_group['index'] = indices_of_samples_in_class.astype(numpy.uint32) + + # Adds the spectral embedding to the analysis database + embedding_group = analysis_group.require_group('embedding') + embedding_group['spectral'] = eigenvectors.astype(numpy.float32) + embedding_group['spectral'].attrs['eigenvalue'] = eigenvalues.astype(numpy.float32) + + # Adds the UMAP embedding to the analysis database + embedding_group['umap'] = umap.astype(numpy.float32) + embedding_group['umap'].attrs['embedding'] = 'spectral' + + # Adds the t-SNE embedding to the analysis database + embedding_group['tsne'] = tsne.astype(numpy.float32) + embedding_group['tsne'].attrs['embedding'] = 'spectral' + + # Adds the k-means clustering of the embeddings to the analysis database + cluster_group = analysis_group.require_group('cluster') + for number_of_clusters, clustering in zip(number_of_clusters_list, kmeans): + clustering_dataset_name = f'kmeans-{number_of_clusters:02d}' + cluster_group[clustering_dataset_name] = clustering + cluster_group[clustering_dataset_name].attrs['embedding'] = 'spectral' + +Putting it All Together +======================= + +Now, we can put everything together in a single script that loads the label map file and the attribution database, performs the spectral analysis on the attributions, and generates the analysis database. The following code listing shows how to do this: + +.. code-block:: python + + import json + from collections.abc import Sequence + from typing import Annotated, Any, SupportsIndex + from typing import Any + + import h5py + import numpy + + from corelay.base import Param + from corelay.pipeline.spectral import SpectralClustering + from corelay.processor.base import Processor + from corelay.processor.clustering import KMeans + from corelay.processor.embedding import TSNEEmbedding, UMAPEmbedding + from corelay.processor.flow import Sequential, Parallel + + + class Flatten(Processor): + def function(self, data: Any) -> Any: + input_data: numpy.ndarray[Any, Any] = data + return input_data.reshape(input_data.shape[0], numpy.prod(input_data.shape[1:])) + + + class Normalize(Processor): + axes: Annotated[SupportsIndex | Sequence[SupportsIndex], Param((SupportsIndex, Sequence), (1, 2))] + + def function(self, data: Any) -> Any: + input_data: numpy.ndarray[Any, Any] = data + return input_data / input_data.sum(self.axes, keepdims=True) + + + pipeline = SpectralClustering( + preprocessing=Sequential([ + Normalize(axes=(1, 2, 3)), + Flatten() + ]), + clustering=Parallel([ + Parallel([KMeans(n_clusters=number_of_clusters) for number_of_clusters in [2, 3, 4, 5]], broadcast=True), + UMAPEmbedding(), + TSNEEmbedding() + ], broadcast=True, is_output=True) + ) + + with open('label-map.json', 'r', encoding='utf-8') as label_map_file: + label_map = json.load(label_map_file) + class_indices = [label['index'] for label in label_map] + wordnet_id_map = {label['index']: label['word_net_id'] for label in label_map} + class_name_map = {label['index']: label['name'] for label in label_map} + + with h5py.File('attributions.hdf5', 'r') as attributions_file: + labels = attributions_file['label'][:] + + for class_index in class_indices: + indices_of_samples_in_class, = numpy.nonzero(labels == class_index) + attribution_data = attributions_file['attribution'][indices_of_samples_in_class, :] + + (eigenvalues, eigenvectors), (kmeans, umap, tsne) = pipeline(attribution_data) + + with h5py.File('analysis.hdf5', 'a') as analysis_file: + + # The name of the analysis is the name of the class + analysis_name = wordnet_id_map.get(class_index, f'{class_index:08d}') + + # Adds the indices of the samples in the current class to the analysis database + analysis_group = analysis_file.require_group(analysis_name) + analysis_group['index'] = indices_of_samples_in_class.astype(numpy.uint32) + + # Adds the spectral embedding to the analysis database + embedding_group = analysis_group.require_group('embedding') + embedding_group['spectral'] = eigenvectors.astype(numpy.float32) + embedding_group['spectral'].attrs['eigenvalue'] = eigenvalues.astype(numpy.float32) + + # Adds the UMAP embedding to the analysis database + embedding_group['umap'] = umap.astype(numpy.float32) + embedding_group['umap'].attrs['embedding'] = 'spectral' + + # Adds the t-SNE embedding to the analysis database + embedding_group['tsne'] = tsne.astype(numpy.float32) + embedding_group['tsne'].attrs['embedding'] = 'spectral' + + # Adds the k-means clustering of the embeddings to the analysis database + cluster_group = analysis_group.require_group('cluster') + for number_of_clusters, clustering in zip(number_of_clusters_list, kmeans): + clustering_dataset_name = f'kmeans-{number_of_clusters:02d}' + cluster_group[clustering_dataset_name] = clustering + cluster_group[clustering_dataset_name].attrs['embedding'] = 'spectral' + +The resulting ``analysis.hdf5`` file can then be used to visualize the results in ViRelAy. To do this, you need to create a ViRelAy project file. To find out how to do this, please refer to the `project file format specification `_ in the ViRelAy documentation. Also, the `example project `_ in the ViRelAy documentation contains more information on how to create a ViRelAy project. + +The example project that was presented in this section is a simplified version of the SpRAy pipeline script that is included in the examples directory of the CoRelAy repository, which can be found at :repo:`docs/examples/virelay_analysis.py`. diff --git a/docs/source/getting-started/index.rst b/docs/source/getting-started/index.rst new file mode 100644 index 0000000..40a7d21 --- /dev/null +++ b/docs/source/getting-started/index.rst @@ -0,0 +1,12 @@ +================ +Getting Started +================ + +Welcome to CoRelAy! In this section, we'll guide you through the process of getting started with CoRelAy, covering installation, key concepts, and usage examples. Read our :doc:`installation guide ` for instructions on how to install CoRelAy on your system. Check out our :doc:`basic usage guide ` for an overview of key concepts and how to use CoRelAy to analyze data. If you want an in-depth example of using CoRelAy, read our :doc:`example project guide `, which guides you through the creation of a more complex CoRelAy project, that implements a spectral relevance analysis (SpRAy) :cite:p:`lapuschkin2019spray` pipeline and outputs a database that is ready to be visualized using `ViRelAy `_. + +.. toctree:: + :maxdepth: 2 + + installation + basic-usage + example-project diff --git a/docs/source/getting-started/installation.rst b/docs/source/getting-started/installation.rst new file mode 100644 index 0000000..96a86f6 --- /dev/null +++ b/docs/source/getting-started/installation.rst @@ -0,0 +1,32 @@ +============ +Installation +============ + +To get started, you first have to install CoRelAy on your system. The recommended and easiest way to install CoRelAy is to use ``pip``, the Python Package Manager, which can install packages from the `Python package index (PyPI) `_: + +.. code-block:: console + + $ pip install corelay + +.. note:: + + CoRelAy depends on the `metrohash-python `_ library, which requires a C++ compiler to be installed. This may mean that you will have to install extra packages (GCC or Clang) for the installation to succeed. For example, on Fedora, you may have to install the ``gcc-c++`` package in order to make the ``c++`` command available, which can be done using the following command: + + .. code-block:: console + + $ sudo dnf install gcc-c++ + +To install CoRelAy with optional HDBSCAN and UMAP support, you can use the following command: + +.. code-block:: console + + $ pip install corelay[umap,hdbscan] + +If you'd like to try out the bleeding-edge development version or experiment with the included examples, you can also clone the Git repository and install it manually. The project uses the Python package and project manager `uv `_. You can find instructions on how to install and use ``uv`` in the `official documentation `_. To install and use the development version of CoRelAy, you can use the following commands: + +.. code-block:: console + + $ git clone https://github.com/virelay/corelay.git + $ cd corelay + $ uv --directory source python install + $ uv --directory source sync --all-extras diff --git a/docs/source/images/spray-workflow.png b/docs/source/images/spray-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..428cc99ba85c0d7cfb0ea9efcbe1bb7917f84372 GIT binary patch literal 554758 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:v=YUf}7;4>~8jJSdEk@A3ywT|lLK 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
Q9PWi?E+G5ehPL_=-!5_}^H& zyD3Er`eGtQn(=>^dx6$D;y(4`r$;qCvvu&4tU_hfMFk8j^5|~OkE6uRJCS45!6&pU zVC?)OPs!Cj95vCXJLvQ;%@ybiSTfr3HZh4etQLgICcT#01X>ZB%RU_Weosrj zI-r;vXkj>}RcMc@UE89nVnqx*M?Vqe3&w_0F4g?_UkoZOm;ROgN+lxImj+x)kPsM? zYueiF>trEzpk@#YI6>9aVBEf>4mXPaY%i@hz9?g)Oa(YE=VOD5jq>>*lC-U^|FCG& z@ZkkFv01?%RSF7F>^2!_f{crk*!|$f96fi45CwcNVVev$5m2cG?FXo2!x5)K@Lc3t zf+A9_Vzil3u@gp5ea&f@ip#WY=WVoHt^Jq2fnrkr9ciI4DNgUG-{=?R>N#vny|4U;TsUFm%8TeYc+>#H!vXj$YCv{VW>2)vE8 zT-8YpUp3FBS%DYy4&ZSmxvmhPOhgR51Ppu2-jyA3a?4w6PSDCB_(D@R$$w^2b?^&nGF0=qgofIfN%( z>HWIn$nOzu)i$}~t)+RQC21%qRIq=6DaJl;^!(N{pCS7R0fTEfecHM`LU?(9X;X#_ zBI_CmNc_vxrHt3l!_&kDQ%Nyq-U*yJAt#nz$YOt`+e+#8{vc`34m>0aV`nwApV+^p z7{7K8fs^dag4YnK0{s7@qv-TOa<-B8Vq9ANypIy6<6(X?m>~yk8=@Im!P(kb3sDF2 zPU7;E#BsD-1^pl-*P}-Smc|s8LI_*SZ6dI=ufBnX zTYm||LkGuj*1INkW7!%%lPnYbyay$#VLyg6eB&3EM~xWZb`ywa8u0#8 zZ-GilGx5MSHy1^Cq7flwBb!J=0n_1*arpUFOmp7vNdhF>4$F$AKV$t3X(MTYi;iel z*^Y=_iF;O2u~=KuBC?mztAlK#Em3q4VF{!^ux4K%bMA8vD`=nGEtK_5VA+yejzS~3 zJCB_{6FQJxFJRw>>R?y+OD%D1`dVaQSqO-IT5-T20H!FKj`6q1nN9KNu)=qnVy-{( zYiG6X9s@*j3hGD@S`Z~7q##R%HpgAVq}4FT^Lf+hK}0r3;~oY(Y!SO`PvUX))K%Ax zb;j=7%tpZ+llL9@AW;}D_F-YL9x%xim3~YfpgOp><>z&5e&hoB7Y0>fR6QYNapDg= zaiZeVwVntLZ<1{5nukEW?u(HMms$vJn5$sVK8RAXyeCoZwnx0zX)Bke^^Hl9-&o50 z1uZbt7d{_%>pQha6goO94^ugZ%NEC!_>Cx;=N7pD&mEKz0&;o0qzS1)dR~wWU8Evi z869+yq>bbhD+M#y)$kUnQZ+U~%X}#?8TWcvSS9*|3LC8_foYhhXqFliml9JQaLUkL zar0I53aUePFRb#bjm#>$;UtX&{4Oltl8qlj;tH0IQCqR9Vi}*xV3d5j5A67$BiJn_ z^P9#?P$Vkn%9e$u49drJ?W6x2qkx8vmA#GjQkz2BL%O-!@gs z7e0Tc665c@h;cjzhBW8zPNOYFVQ%g|dhrMFyK)@Ez*-vdT`61$@YsR3VTy6@*1wSh zY{NF7f9PGkcnOq|2?E|vI0vVW{=z>D-IurS0iZko)zuX-S`8NQg%p&8hR`59G*V8~ z;ECOYgyY8K-yLi|5Zj;DkTJGDBR%PbYu~2sS{&{ikmne)ZD2if3XA%QiP2PKK>@*< z3XBDu;gEZi<9cC*0?_OeW{gd%43+0iQU%5G_s! zlMU~(L&>4e6o~D-wNaiDfce1@GTc)$$74}Hlc!NdJ>cy8`g-yB@TkoaSrjC8PtOw= zz-x~@m%lJ#Jwa;X^(GyjH&RKhAWy5JFqf7ohbZZ79g%*lhd#yHzZS4)(o5y(Fby&7 zkVTBZ56Zx|BQ2l#=Zd5dtIA$u`()dEo1t{My^bLD8}r@*Lkwfrf+YK5A&%DY0CI)rEk`Y(M`UOw4{R94(&5*nJaZoSh*QiS zYeo>IKo+kR>$E1;uff-jsq?+Q&BKG!<3~O7ux0i0wZF%DyU?SA(iB zC8aSz`PCcS?KJnv#f|yr&}VSufTf3B{7w9Y3-T)_!HHU1g0`d&>TefFuz|WG`0K3T zAk?_9eIiSgr~cB14e!fh{2Mo5OP#13vxwO9!=Z+n-JbpW_V0-dJC%edU18I_J^?I; zBYXBVqD)pF)auxJGQDCt=DHtutqew>zaWNvX8`l>*5H4$*u?Rpa#=B)3CMm0JkNw9 ze2+(h&?Lz%d(cAJ=r}ky?>?{h7XBVU0wE3XhkEbr>-gX3JOF-O!&jg`muk~63WQPb zkL1NifeZ@A@FyRjUH>_H`<>rR;I~cCeA>9mF?bG?_;LUwkuP)XTPS~fpzyzDvqDO~ z&(uc4z9iv;7!l_NTpDIeGAT0fU+{&lzkC5_7=Af8v?3hRRQPkXH}?$uJ<+C($6Kx` zgYmM1ECd3A2&w`)g|c`iMTv%47CwUW#X?9-1H5Hewv7eoEtm!rbp=g8vj~^<19nU4 zIQRwO=dl~A=aiYRgrf1NF05=*7!=b2GF1eM`X_-%D*6eUj{R}rV zLR=crvGY{Is!HZml88bUe$t2u7bTL%iCA5|8)!r#<^?-#~nCu(i$<| z;lY~4tJfk>yEuEoFzKbAIdVMU!)OrsmHPb?+s5kMZjX^jAqa-DPMs}X`tZR%Pw|(# zLE0GhJj=vKZKwds(J!)g1nk&ZdglM8Kh|eP2_QtxspZ>csaNGjkoMmonLu6)d=uWE+}~{qbzS7+gwR#J-HBR2YEW ze8YOm747a*BQFDi>$Y+xNXI%(_GTsp@o#S4K=r2d)(5o_Y zbWU^*aSN{9Y`^EX6d397-r4ND{P%ZZ>i$S7Gr@$9?=-z_AgyJ3buclMvI*?8j2}jIn%`cZ z-W}6v>FLlW=H?HypEG_&$HqGE4JY79?AjV#5xt&cyv1Yq-}f1K9Tx)!c3_77Fp~Q` z5)%rXo;)a_^62w|c4s4q2;zzu=JIvSm8ZzJsGzwq#0H-sQT9rV5h%D+U-!-_mA^5z zZl>Z$&P`AgQqfW+C@_NOaCebsc`V9S2ZRaM&5Qdp0vb}GIQb12Xw3vfIMp&Ppv|O5 z43HIQm)p(+<&v;y7$*mPy!B{_G(F6a*wo9+k7Ra(2Ci9T9k6;omM>Jx#wiv{%|ulU zTtsl1|0>hO2CB~_dKnZcOlXP)+X}Rm@fOIHpV?>x>jMzQP%mNRkgC~x#KU+~-P`0f zh-I}UKpLr0a^x=ykr>76VH6P1*vlOnJ$$$FCX@_vFa)atd*)EfI;4B6g4*OlwQw#t zLGJs^Afk1KtaP&T8Eg{v9#nmFrXrHd-e(vXa_5XFc@LW@-)2<;OPAExzxh_|Siv&% z=sLRf_!Ov>+DYH%xJABwO)z1F))N|!v}z9Q)s@d+A}T9LK&%~4%FJpB!S5|L@PBb& zS_s}ESt#oLjv+ewBJP#uJEdx-Zs7LnIXv*8vE6|`v(d3|rgW}e5EsUbg^cqnn>Lrw zlic^XYL+{^gb_w*Bq%09j;EvV-?8H@x@bXCj88Y66e>1<9DWo=sa;KDyvCgQT&*7M zK{bF?e%chxewLE@g)5~rnMq>t*(YncAOr$`1{Z?mx+03qjwMS0#=bO(mmm`oK-R>1 z3QGio*l^-fp-pKw;4ZhER4GXmjy0G@TPn3YK(bR5ni5Q3Rp%V^z2J^)V%{Dj-Uih= zZ$vF8@_3A}eD;1_*ZUD?uts*hE!|)QIQp^v7pY|2t*o<^D;)3=N1lrxMEi3Pyy0xd zs39(FX3e2FEM(gBh_C;eA*@_%>)Raq-Z>O@gTQ4e!zC5#Bs$pm{n5ADSn zSxlw!AOy;Z+Kj4r)OTO75$m@xpmIm#hw&B!Q)Gc zyKXUl+{1ruuZTKr5BY*)mubXOTMyG;nv8XCL{9Q$|FP;kj{aK32;tLtl1o)fcVVkJRi|~973}ob4onuEt9<62 zhjy+)r0;qG;f)J)0Q>!Q@*?i-KF$Ep_&H^}Dg!%3pr-P8RQTQmd*^@wEt4;nmcTMz z$H&OEjro=ZkVsvUlYYM>VnV4S4DDwUXX#+k zxk%J}$`R7srq%2XJXYg9rYH|jhw*t)yYk#J9 zz{gu(@~cLyu)flVDH{C`sG-XbWD{R{eqXuQ_>SQsS7!&6Uj}sfLn9%#{*77j8 zmJ7lIMKatiBwC!S z-CAtenACr^)8I2w)wZW-i&&a zDgcs1$@;wB z358bDK&My=k&qe&_HKqql2oA_MK^B$$EUaKOe6m*JzfSaoSywmwK!9V&*5q_xu3lu zGj5G1&hm*LzJ{aOfI8a)@xG<|xKt!sMYVN5HZ%u>$yLxLv0Tm)0lO03`gF zDYH!>zYzU7zJnZK-Ee94do0-=qVpJhBLwij6-#!^K9vxm@?xO*)BPK+cF(S?Dcfg7 z^LJi6$k-n}SU@on31&iS;3>}_@0h;IwqHTCG(7;IKt zSVH%jm~h3TYz54 ztlSBUHUtZkjSHtL=0Im4;R(LY3XQ(qKn?q%oc^n zL72FUm@?+p4cU^4vK@;Q9+C+J48>Sf2fVeDiQmwu6lybmifPOL)bLnwXd0=F_9|4> zFT>QNKvaXoj7yrGQXHl%wt`@8F1dxTCEGYm-fo~JbcBq(4v$=g#wR9cPu&&5od1Q) zUYk{xwc{ovELy&TKV*B!rt3I`hQxSp!Js8a@+Y(IR#8g`+)Okv|10Xkpqx zTQEIv?fgY6g~|Po60}LtOE{2yPEcw_Y)EQxPRw~M|7N#B;pEO=m?@EtICAkiODo7O6m3T5m3FJH=LY5b7tv7TFOpCqwVdQ&d@?AsuL$BW-UIaVZV-2gbx-5bdeid;+fHf{c z!IB)mT(zNEdE4;ky6-88?I%5+?dJ-5{k-D%`=p3!sfZ9f90PrNs3^qvI}%_hu8~uj zP();o&mlQ|q8LeK(k{&un4E$6IR2?eVVCOl`R$piYTfrmkUK48S#a&`{=b#k8oZ>W zfY%gbnfYHOz9Qal7&XSv&bgy)gF!wi_Iiq!N@wzbRE?SLN2(B7XhC9a@+rV)S zgRAZ2e?Qf4d+?sf;k}lcJ1MXIPM^*|H_uW9j>Gth2b({=x!x=$wlD zKHDFpD$c7JwZei=`Jf$8TJBzcQ$M4BccgEzB$O)9_}YKjuO-W2|AB;f<7X_w!J|bS zt3S&+E`>~j@2_SS1K5ahYFY3Q1&w;S^23~Bhtj0OudP@RGf9vlH#`jk{ zz*Ic^;dlJQP@z#;a|-3--sMj~r~*^!>-CWau!z@wuMoX67~j>$c7-$i zI|gu2;hvX*-xDfkbaLi z7yM!^#_ut;MrF7Ws?*>|6lhA?oB%Y=usi_0jFopzw#%~k&>WfsBj3FOqbA{D7{rAQ zH^{%Y#gm_w_GZrx#1uTU`9wF)Xhu9H3TM9SD`EvgI&JEDqv{`fjN~D8Z$z@}ZmtcVmWgW?B|@!A7o$1JU$D zSurNE&|-DH=JY=yFZkbWd!N|^9EI!ZuKxOZk*kR{#ra-w8~9(TjFSrthwvNqhY+06 zsNgilI*>wDLlo7C%9s(e^IU_pVIt(CEau|Up45^bpp>kuk*ye~rBDt05nQAd`-o5! zi;jMEyLpMc?1p*VKW*-%%Xg}se2sd@`J8W6XI5llXNQ|2$QNVx#nlIAmS@`fyJbmk zpQ2h@(%cQU9Yso@(UHa!E(9KeBs8+ai-g&;i%CHuC3POTB`V1rRJxT#pM*_m2p&;E zG@=Tj1&zq>L)ip3&GBtD13NTIWFG(%pWC zWPf#DkNYKdZMOP6R~7vBVTbKM8Ed$mR0zk|{CCJJiZAG9!kW)v!io#mqssw*_pH&| z+1E@?zh$@9iSEHVKDC>>9;J2z4oJ+`wWErJ(HO?*vz4~%_^N2KjQX-TjNxMlyS)syizL@mEBz(3G-mgdAVXf1IIHTqG!iXrfASXDU0oe;U|jiU>*)*UQ4Tb3%DaIrs6NpI4Y$>8dFrq3JJ8e+V8p$u}i+33#k{yb| z5YS^vZiRH~MmeD$ib&dIVS;vGWTQYK1&J0c!EA&0geVc(h>Q%;lEVTTKcr@@%$1d6 z)IXHrWc9haNK6xc$cK~{FoU0qkh71RgStdU&c;3$C&PZNV$Y1po4eS`bqQ2083LAh z9jXY9sErJ%w2j}93XiBGEsQ8Nf*|=KUpDWlQE9qbleNYYkr=`+ZOU+x71evUD3 zWzXUB_qO!+kmV-1q|Ho>xM=vVHsl!_8|vf!?Jf^>4s8tmlgwo6ku0%gB%z6*QsDvX z`8QT;+Va{!)R#<@16|m-ILkjs1=OX=2qIQjYfdx6tmh2qB0{FE=B*ljcwBFbirfz; z{awCe(J}C*{WV9OnSS`+CxNM)$&bxhCt$ zHONVpB7Wwdey|HJvStwmwiNDs2_Aw~5bOy|1ghQUE1lR&mwznEWYc7~k zs>yp@i@?<99s80W-=+}84ZrQaK1?N1?B6WKKle^`Q7mqc;YsD}@N5bOaaK?F zo8_&37oorQ!kYi|JzcsE%;0?FrsgcQ1Dc+T_#?$!)mKF>CO}*L&)j6C?CMDsgghwf zU^5SHAPU`7^?H^8H%9ZoH@Fv=>7J!q6DVuiFk7^jILELCu9O((NFamuZCM`K8FDTTF zZ_NX0h9$A$gVwd9@I(>S8z&?gT{D*Ep$p!hw`guIU`%6h7OrY)RiPJ!BB4|*3_Laj z7HGsWCe#x|FYcofH=8Ufo!`6ISft`DvA9~}T zzdf}*4E8R|p(eKdT}S6kEU;!M4keI|yiqHCwNjZe7&?BJ740tkQ~Bs7Rt>4PzP=wI z43E6?HUQ=^n?t9k#eaGH9mguq%egNf-VF`+m79;1&?sM>oD!x01Dnt5RW3PHsJ?B+ z1fBouq5mY%dkDgWspJL9uS)D4$p9nFpX2O)=g6l1fWn7xk2@K2B9LZ`_Is;>&t0r2|h$B3w{i;F7D?7 zMaQ6bLr1{DkXfSkFmn|@Y_Y$B!Xnw8ZsiFmi)jny$A0PsVM5c=KOLO>>k} z-g_Nvi@3C3J;r(5j%3TE@3;!v86>Lkc{}sz6L-D;1fy{XcP~6}BUPKmb}_^UF#MlEabi~_ zr|wbng6LxB6m9PROnl67KjMD3Rfiq<#3Ppw5zEp^d0v&vtbr7WT=QBVT*Tw1LA%8? z=6=jhkp+ABIk$oqFJoO6UF?o9&gbqEiO-{HE2%8~nJH7wMSZVpxr%;Dy>ndZ2l-gO ze2v{BDdN>kc{;dt2jY*o=8EcSA}JXG01sNR{(HQuu;^tkWpVEq%ji)te7@6U(x<=) z)(!_r>ImiKt$;kNS!!2kZHffit2w7vIPn30zzvG=sVVE*B%1l{Z$~*3YY)yke9Uis z4W;F(-(QYI4{SHP*B>f*wRue^EaD!Iza_Ts+9$|5vB@H;6H1jR9l7Ss(bliV9}ZJ< z-&-;VtHj)}r%LORLmvvnn>IhHfvPIUo0>K(q6MH$T)YC-9|?v};NURW`Id!d^E5^l zJNS1+o(>6`omW$FYPTqa3LI^V6rib7aFjyhQraR-)tz}~7lkAwY3Ex5Mzzx2%eknR zz99cLXRC$L)o+XKMM_Vm@6%gcyZc~*G|BTRTDyGc>?JKSUC)k7->^@haM`Rj6*vwO zf(Ig<`%p>d!XAb1N&rs?OAPP2d>k{pMkN)KmuFZ|3TkVEL?;emz3xexvE8-}+9Yv#eyC9+iQZ^S z6X&G&h8JrR&XCgj0l}y7{ZQuL8*T=!W1v(>J`o$s^7<}rM<_(PiO za_Qbdc7|c*`<&Ef&HwG?owc(MRA0B=p*X;Zlz^L?8}{Z8a931h1R727ulvTYAu5@C zq)iybxbLWsW`NswAJuh_0;JkinpXGo!^n0pUbirsfd;=el;ivJOAA0I;2cEl^nUly z0wl1Jv**dP^NTot5=xNx%ba*jLn^TP{0@vfX6*PpACF5O=1aufW*_E9q!TJmK!$Mu2H{5FP*+!X3&b>uP99~<0{V!;y%JbUm~Id0GFFmsX5~! z9zz-7rik34!tg2_A3w_^`?f(ICzqmGg`{tVDr82$5&;Mc_Bp26pqfPIGbl4Y7{b44 z5BqhLte)1++s&TVb9$;)t@hvwqOyj@eryPz4Up>pP)TT~o2tmj2+Ue|!0^$cjD;+^ zRMS#}MQ)TkC>wee2*f0wrr^;1ala4sf7_1H{CD!l+F3WaT(+EYeC|)K@zE0$!2Il_ zUqk3MV2Y{obaTWM86tBib#)Hvm}wi3os$X!BC$0VAwn}$w37YppL!6`Nd-oCuCd6x zyvQrRWTJd?_{j#7U(j+sI)fv?}^Xayfoip?ORA3#P}F2irECG^Gw7lCS#1K29> z5jm%@WYR0+JXud-ggQd>Vnyx{F*a||$ow8Z__%>KZ4!>Ml!k~k-BLm(v9oE1K96y6 zF-^BZf#UvphSD6*lrrPvAC@i_h;&ej7|EOAo{$a-T~g6|1UkhRX*us+u)&-UdPVkb zVd4%Z(Gju2>NB@vgJ7ie?XbS`-R^>V@P2gVs`)*?efj+PzbxYZ=NY}Vx4VWUcA}^6 z8&h3(^LiMQrmx4g&23$EqdPY;2v2Xz-d7CZ^2gUVw=>_=U;q93&(}*k)f8XCJ$@js zqBcCw4>|@BnQA9>m!FL@B4X3U*-tP6TdiP(J)>VC(HQ$??@npHR1%AXQ$`jut;aOm z_tdSmbw$(tpJ5vdC=ZozZ@gNM80?K0winwX;1#@z15r zriCd+FT>5!?}Stw9p@~0m#oy5&ldEtbLgh$ag$HBkj||HSXNv>%UI?3nJ4kjpu;iI z2WGnrV}@A;=Dgm1{_Sq!{nfvFWb+Uv*~J(205-=`_+MB#_TStEdh3JPr$ejL^-nQ~ zqF|j3;P%qmTxtIUjn(>qIiHR3mKLEvS4?wkyH_a*?r_2XWmNk$b0Q_QmOg%KbIpWd`@dc z%;qAasVhY3Ll^oY6eot%OWZc+c^foWB~0GWI|8&SJfcxB&+TgJh%2XMoq))L5{w-T zwRs1gt0z55_{pfKsxw7O_&j67SEfy8wfL*wNoL*#TV6#O->X*qQd&9CA6;>TMYCu< zCZPc$tPv=|7{`Hn(eYwGs>oOrZ{Ai<+DR4S^mo!6nps0+f^bDwxRTV9=-E~+vd*bJ zV!7$?gN^j+4BGjXAkhSFg`B6VZ&`yd#`6fD1FRFS1_Q2!DVbgN^v6kVjiC8Zb+A6C zOoRk+;9+1r%n1PRtC;`8$s@-asVg$wXKU;&6xDi}>})IekC&dBJ4x3`!fUrEQUiQm zVSAF&YhMTwF$HX5*dh(&uY%zM1F#7!u24}_-BSsSh)UM9 zv~Qd3Q>eW?0)^0UGU|VNmY^tBW22{xy%34G#?yzjiq@v7k=Xh;jF8oSo9%N?{YFp> zSz^AVN`1YjpVV<6VwoBV2jfP2?rYT3nN?^|--1xb01T-l$Jw1i zz3J3Fs2u->)H%kNRu_VdBsx&jvF;KY@JBh_%>;3n2b&IrU_aA{5a#0|*?jx9y4>_6 zk+L<88J}LVnDy%GQr{ZcC$Jb+ZX!^s$N`l18dvPzx(X-g_9tgui`SAd;VD6Uw)Cub zH(%IY{D~`g5WR(t89}EVDRcNU3nMt+wVua}r_#%RD0_>8h8bk>wRa8OXRaQfBTDe8 zh3+Oh<0@o9fgdj>ZuM!6b-hoNrtve27*isM^dGJXT=bs&;NNVp#C0ftbZzr^SqAz8 z?|8$@3pm}>xO8`!s&@H7zp~D+HI(pOdAIYgEt~V zY4-AXQzzC5(1CwnL;8-uvtQqVfKzZMolxVgzW( zx3`U}(N_;5P9$~Ts-9H60LT)wE?~9!{{8zqD+4&UK~2xqZIZV%H8rtn3Ie2zhra-A zC=sR`D7ynYA%PODiNK*sK%fQioK08wSmv2=p;~D~;^wV_4A2e#JEWH`q@jk6<0n=q z761?q9?vz#l-0P-q$b?b#@)l<8zV&qTFEV(J*f&Pd+ceVd|dsK(}+Mw&>!6b?chb# z2z*%y=2;T4ANOX-F&8 zEY-5@uY}i_FIu+yTsaAqw9|cyOG#>z#7#PW_k^_kVhuLM%qwzb3U5q=Py{=5`e`RI zZO=Q7-WV-qs5ooPc1|~ZN-HV8R`GZFX0ZP1xFGjo{eh+Of=(_>rd2Y-2DF7B3ICOM zl$${(Z?b$PsWJQVBr#>AR@Y9r zMcw2gnW9AGHum((828Da#%=kdlAHo+-Dp;KbtP>0AnXty@_`L#c;-G2NeMA>tUYX| z8~9nTG!x8cro|tGg-B8%H7bcRGvTO;0hSUBDwy|HyI8ie%n>9YwKbs+_DC-9Q&*Bq zJG#^ra*vTT4KxuWiCLnqOpNXfYyxlJ*B)~=sdLXfWR1H2%HX|_Z;J8|j*{K}ZaX-q z`!*Qwb4PaVi!qMztm4<%CLFUTlKsCdz{RFNvGd4|aTTczs=F&{oiDqR$0 zt@;Dn^ynz_z`gsTp07hXptVdq4CNmE&TTyVpO+Pb!09hfO^KfAD|G~@Cg}zY6pBM{ zg!V6bSyIb@WzR8+4_g}0eQTv#&y2*$EAGp9+k=B+*#- z6sQId?Lf%XMi|eh7~k0ZUkuKxCVz|eWz`?te`qrie}2k_yKhoQs61VGiTsb(V&vri zKKrz#0FUQAQv#rYx&gM?>*BomSYu`YhdS6~B7?WzY0beg=h3gOL9Oc#hdbbvF0vkdrx5qk zi$QmP>%7YTu!&Wv9rf#V-ToH&PBe zq6?+umRUm}?kodsBn06b4X&amaZRpTx5ThaA}@6>74U%HOW!8G?RdGFSQb;p+9a(=gzJ;q%uMW4>6~MGwHXqK*Fa!dF!h5SwlqHa{N6p>DJKyBg)7 zU$2N6A!QN2bcHJ=lq)%PRXqrCa(&|veB z)_;V@*I4h!#G|j;Dd0C!@ONbYnFfPgh$dxzXt1d2PzeB@W_e`c@k88={C>wRF}?=N zK<99zU#d}Yg(P;@$A$HLZlFcP%g6jnw9^0c&~GHw*f*@Y^T)G9`a0gX&i%xI&ovkc z%r)%l^&-;p=w4pZm~M1789tqnVB$TPh#TBleMqLdt!^TX#8I*jLjtyl@y>&o^%!=^ zZ&KYYNjZZS(d%&oBHdi#* zp3gFj6G3@rFTEsBJNK(@QuiLhU4EDUTYC?AfNpL0*5n6J5`|9d<&eBk)zUyH z_gp=+HzIUv#zooCp&XL_(_)MlCK~3Y&%5jyKMyWrx+ZlCE}iOB)0dDBX9#5D)OfDg8`OK;cNi*p(`{??`xsm+?XiQ}B}_U8fHACIWX__M%my_k^_lnX2~ z?pKyN2LU9iz}xc`;6VazUGJbTAfzho(E0FXQed~ZX5s_EVkO`l??m#kYU%jw ztYpCkh_71nWqRbuS85l|SbcrI7{Upq1U!`2H#aq=eemz-;`c?#J9uk^K6e~A(*qK0 zfSV4qj^2L+z-s~I3@5-}&{6dCiE-e#BzFToD+%g+R*9W9H}`{r$A;&j)0)%J`&K6D zy}JYysEzY9nOL)$t7M?8g2T{Yp8s)WHlo$KpCYjsd!h=H`Qc9yxVoh-x3~~c7gAGv zUHl>sLd17t7I_HYA9mKV&TWs=A$^pr%q#7*&MF^phF5I--09a z+koyJ>5k=5F+BN-uZbP{&?vuQepv;H?7o_gxT5D-L92JQv1-UrR*wEyC3H_9t5U!~ z1;+N-9J+!@hIr&`=2V!6+bb5H?rF^k<)uJNn9;44)qX{$REG%OTY{u?i@w@)YL+PQ ztjO@p!V5D|?+GIVA+V56-k)e(s~i5x_DdO^WGl_CUn<_Gllp+6%)d|@b=xh-^7|(Q_(pTqA)W?e~XsR zNKUr?hj#7ip2%Y+vY-F4iPbuBXQkg_kp1)0&+G?dq;f0)e+4z6D=&=m;T6A-VeAAr zNHbE|CxY(@lSAt8sS~R?7P}wCEzNh;jx54~WZ%sVWpr0~R>b+;!bKl;3`akt7?Z~J z2W=;PSp$!0*uWJ|1=K!cJ)TwNOis~E{rxOO5hbb*OnQOHu@8ci^&h`2(~pvDn6B;t z+Clvc2qiHCC^XhKoN_zhz%${kXf6Yng~R4!QgT=i%WPIQBs7J;rryPf34rhGj_!!={3<;Zrk`2TCU|Kbcx`Pu{}R4 zx#hD~(PqXeJrIhC$(LV(&?>ZYj>ed$E7kE&B>61xGK*lX=x$Xrp8puH#GH6ikHY7` zZ+>KmG%qsK;>oLtBF@lh*o;YuH2mMBbknvM^7hjf`Lq6td!hglg4dO@Nba@xhNyN4 zrkAVRI4D&ORa@`zXOyw)z2%`VKKbk6!G7CcI?WR_s*{@La*n>MGXlSyF*Xr5?WwK^ zo9xpzc+ZKI6qIwve50=~XD-et_MIpDF9MFlZw|%R#U*!YPe}By4_TO_? z#BiPz=BG z@BinUU1^8{93!a)Y(s#jatNT6j{)DlYffNcVdqcWR0Xy{fDp4(yWGHKf)^DPwcYN? zSL{g(v1fJTmhX4-4)Xh;0AQvYh@A&wuw>O&DL@eh|M|z3v)=k+DXTuQfjO@DH&7N; zCmI~0|I=tMtdWfAB?iu@wZ78MS+0-BF;kOcBu^=WHAu`L_WN*>Y&mcvm zp6+4`>$G!VE=L$2r1=TP_+le-vprc=vL?GXIrD&JSk~D=Eq+Usm=jB3}U;{fpW?3Xj^{g?W*wO=!yf2@*Hzit(O$(Eu zGQZeJG%4HjIL(`nZD|J$atwS%Sg3KT*vjjkQoXR5U94Zdg;dUfl@ob570r#J$eao^ zawrseq==t`4r8038F5Y1`u-&Amq)~7mARcy>Bdy^efRIBltk>Qp+$#@_xgE5pIb z)yrupmYq$joQ%2WOjy?yqDQexPz%1NFCI_+srtfy9CHK?WCtNf(bslkFxCo|O=zYq zdeZBEBcp)JY!V&8iF*m&ruQGO;n7>%>`Q$_)c8GFJ8d@hhb_Z9QHi|k%Wh_#q|`=d{Ydml0ZVZKt;CY8MG2%mndL_-(m!tlTb7* z?l2xFCzthqMBe;=Je_q^)L*#uX@-(cNogcRP`ahNQ@T@1TBHT(Zlpyz20>}08>FSX zyX!skd+)t#$zLu89M|`mbM~|M{EWH}gh3^C>1H*`UU!+!i&VESu^@OYheomk$gMk?M5{mZ%bv*%@1@-&3eTj0*ZO03(Yavhp&_L61x0cJ^#49>n;2mw}8qt zjPA9*i$6zM5vGnHGv4bkABUjOeQL}6X2h=|(X>Y?6Na;9Yd~mZ)lh@fa=A>R=^(5}k3*%N6NSaK4V4%;+`DVxLj*mUlD7pu`!VbjK8^NOTPp&u| zm0yP=Jk2qKgI@L*D2)<`6Z0xNR@WMuY5EZsq-Vu8dnGn|fnP z4BYGl#0f)9A%jg}W$KSMd!IyUMy<_k-r6bbKc}@ zNg7;NAREbvCMGPMI~o8*!49Z@7R(ZSM*qh&YTnTdi|k8$aNsMQEdDpy?%V3J$ov>= zZ;9yXyNlM(gg0LGjC$hdvX&|t$V=x2y~5eWRVJdY>nRl!&~7?vrEToc*e`*?Kld1r-OVG{9hQz zL>odMBMZ2Hu;q1ynjsGC9tzHPOEY_rHASc6_vlD!xYE3p^IqmT@ueaQZ2MW^AcQHK z5Anrj#zc_)L0dp@J&uDyR7W=+uOrR0Vxqn$?pY_$5s-p&OSE)#GvwhLGQe?XRIzZ7 zWH1%~=?L{qeKU^!Y1kF7j7M1V_R05QblvI4!oN9u^81$r(KQc9AI*^-&bd;1a@wYL zxmG4}jWa9`&vvL)MibZztSi<$JU<*EH#t@u)P9U;PEU<{ZR^bTjR{!{E>}dzz$95q z#W}WT&e-&asiW=QIqTjB-Vf%}^$X`$A5~d~pU4$fgW2XiT*v1muvLWUb;lRPmhZ@a z-xprhpT|@s1;nn(7{y1=*>b0k`dMq9Mvsx%MeDwk-X1}J3Y^X!-my!r@nG`T@E7`$ zZ;Ym1O4!Anj$5sX>9%oFT>qe@f8XKi&?@wvOTXL}ilPrf9)A3DAkeN0#_wyd2cl4bk*5){3fokw-EP=27Sj*dT6K8s%m&O=%bL5op9-A$!x1ndi)8TYT$c0QYZzIu*>0%QQ zxOfGlj}?i%;HD%Bj!8_`ck;%cHt^<0Y|fI|;N;|taGb>b!^F{puY6?MbIjzNY0PCj zHLIDY!!jDFhlw4-$^TlZu~z0ul-MRdwwukxgeOW$fpX5al{b294rfyw^{K8~r?>{a z6VOCU6mjL$dWmsF^HqxtOVui;zT^|xkl{+>+N9Ubdvv~XJLWK=&f#dVuy_#DQXsom z>?0yL^!PfI=tv~A1HV1aN#1|}5E^9fOe3vJb#TxFay3}+P?EK3m2z>Ec!Y8g;2erN z2s*^15(-J}5*qWYTD+bAy-2}E6OW+_KMqlcN2lrC>QG3_wSm6y<@h!R4109v68s1; zbha-F3bM`7-<8LC8Y7D>(-b0SxELr?WvlE{IDJ@6U(@wDwF4f{-sK#rVg|dDWEtl9 zV{Do)35mS2UozO#5YW}W6?`af)x8cQKq$$a&c-3)LwhW{Qx%hWNo2p!ls%;U7`PDg z6bt=I&&dA1`?mb~O!+Q>X0)2lx8k})HSnKnr_y^vuK&!sP@shCoeH_w1|E0B_h)jt z=*T%*rYhn%yEwUOu^?MNwR+Ev;Z=o8WHpzLfvHviL0J8hXA$^jw%|{J%!=O588W1h zM#T#h1MW2)2EL5>e7myEQR~XMnX+evz{EKa*<{cmaCUyf=5hBdzV!ihbdy?TA1=6c z6Dja&A%clqGGUcT%4sq3al?B*I?GkOHt2R*5zYS9@SSmX+t5P-*%p07R!^oL^R#K& z0PQJfUg^MtLrsX=E<914(9+zaW0rnw55?EPusfp(tznDG1k(t{Owo_n;x8pHiHao} z5U;vaBDW93f)`aE(zbV5e-SNoG8NJ_H=mvTP=K1;Sx1?EpZR5m%~&CPNWaiR7ahG1 z_W*ebw{Uf=>g||_eem?t#wmpGVI!!cm1~NTYx5*g>+xyR30Co#(DVCJo|z^;>|zRn$A@8rd;RYrdf!9%Bjh0Mpg{TDuJKUM z4kkUp!7ipQjsG&uvV(?rurEF1>A-O^kosFJ8Y8$?VRrIOm*|zR-U(BLZP%oqy9{o) zIOKE&vE6B6z=68PH)Zb|$?55#TEWQ`&1#Tv0F-k^WJUsI$4mpVJ#1z}Xaf{8TvL1v z7Q#XaG*sE>oEvb0@?EUd<-HQTTS z^~zanN&}#rUp`|!_xSLH7=zOjTJ}6qoaWCJVU*=pTlY*UsBEKw`A&^+L>GdDL?=My zAVqN7Sj^Xpz;$B#`i^)U%iFQCyKA$ApSJxJ?!Us38@GMiW}AM20-jzvT-DN@4h0;h z5n5uTbr-%wwe>S(mm5Jf3lqW$VsD20UeTq3Q)T_R^5z68YI=GGcU#G6lpi(WUh&O_ ztMX#nPS`EOxb(=`vyITK>%F;%tJa=%gK5h%-!1Ia62w|GfED76?%l%p-0r@>&mmGM zSbSK=EE~V-Luve7vy|YgMIw|yC}0V9lsi7$G8hNF&e`NONdyH2oos@IP;;5VzY(4C z@YujEc@(B3IqPz+N~vNdO}WOm2Xr<+Km63gEoWi~*Ncq8OwiAC-TH=apZ5;sDcG%~ zwL=u;Ay~HER)bwSvqWU}Ar zXhy#vX?c>Q>$DLI$0HFoFl_mL@Abk{zB+DSy5+aZc3&yo>_~dNQeFsK7q#XZ7##5# z$k;3#tTnrnX*s{bk#t3; z749!=L>(GApx8=n!4vqnUvvbA12y>&xcqN|&upmX@{@q`khApF`W};RjMRjeI1lt& ztDySLRaH)V>oDK)($qrcWx6KV{)E&o!JakP<5mN8T;OlHgjPC(IKN8|g@=OA{t`f0 z&NevHf{;9{_P6zZpw+w!eXG%+L<3eaELW&z0+3Un=o~=qZfq)-YOSz=o8_q;-1VB9 zo9p!00Ps?>M8nk6GZmJ8p2#*T*SeYd+0q5O5IWs=JE@dt=(K(kf)#97A$8d={7M3L zIssOrmdwLL$K4Blu-EOn@&o5Qo_`-YJKD4Oi-cEZ3L!`($Yz9MIHPirb|P=s3Ekb? z5)Ye94}Ms%9v*dI3$5xXqfb|*#blqp-?PJ6AL9=x>MAHv7iJDQCD@3@YoQvCq!qLi zLz!t?yUeIu{_$0*Fj1X5pTF8BmOGsUpH{OsG%7>)s;$#4*{7H1hej|1g#5cb^o3e! zxS67dGS5Krg&NDo_vzG3- zLty+n?z~xBW2n?#B6pO{Te1S1Kqyh6AbN2b{|tP#PcLGxCW)1U;)CB}#Ev`T?;h6b zcS#HTM7hHLaSP$>50s2|^$y`?=6Qr+VLWU3=xM7xse6 z%?w|DAf-U=#RSp@TT!afpAYV_nPe6Rr^2E7SS)?inza}kJvu9KbO<9nNCDk{BL31@ zXHlz%Tp+*b&DCe_F3_=nB%;dVn-P|bddlHQq9<4f6Xy!1R8c?@N*oa5HCFE^yNpSx zu17^aoZ1b4nlz}hua&)Bpdu%eu~6y%z1-x*#`QQ~X?hhkf^iT3#p3JxIj0kg%llfQ zT34Q1;uzy>@5^s@rtC7_uU=VKTib1BYwA-&GOtXe+uNrMzIgr$JlV!zjHGr>UVfA35dg#sW}XJR=5yu32X%GfPOT*AV`Fj~XR49T>C zE0&Ou5C&s=X_e(&beUGEAZW+|3M|DxAmKED8jmdJPniI6U8V<(Q1;tZhZRV{L|JQ* zbxU-QOt1BxeAifv# z0Y>^R_P;GoOG+2G;%L2h`Okg_^6#@kdSrOY6(J&@Cmud z<53th@ozrMZz)DtO0ffX+ZqWBp}wkPwvy*p7vu8_?Rk<5-0AM=Lqycywe~bhal3iH z7d*!T-~~xl0-jN8_lLe{jqGJEJNNw{nx^~7YwhpJJivY8(dZ8cAD^W%kMj_ z3*LB+zX*DL@qM^4`)-BMhKh_eQ>4SrNfbnrBlfdq#JGN_ z_e-;@bnlyXI3nZIsBo_mFthS4nc@dP(>3gn(076c8>{f_-cV5B*|h_zz;}I#GPZFf zx)L#&sA9TnbVASXbdoyQVqd5WQ;mk4o{s#Nuag*?f3WM_YCkv0dt15HKiy2!xQ_G5 zLNCGh;i=1UmeVzmPh0ez5l4I2p{LixC#aOuT;$^pN0q=wil#T(gCl!R>}sp~ST&4D z@HGaZYG`9|9=p;@IVX=&CHZOL)WM#T@I75$y-$SPQxW01I0sS&ef&2husRHLnT2|HcQzkZxM?%6ZvGzIHbClBSt?ceJyW}BWY(x9&t3AUD zsR1Y}ee$Err;pbjC#1FYmLu5z_nhlBd%t;jc<|j-&oN#2kD8ZE%f#d>UX{@ydc^!V}dOysHd;U{l2EIK0rptT@d zW7{e68MrtClzmt6VJb4LbFNT>Qjq^L>n!Y?pZ)1}CnV_8^7Yb-F)d3R z8GfnwlJ3B^;>54IjRju~n6`CmQmKvJk%XUW&Q}haR^dfXddkrhw&_Wv(;;O`)1=T6 z2dA<&IQ#GWNe-09=+rl8@Awo-8A@F(LzgB9DCU z-!G8Yn&S>Ngln<+J#7yqAz%Eu<)u^lKBJn5-An8F zX?7SP3SEJ-Lb`PzHJzfGf-Tb)0{2^ykziO;5#q?N%tWmRnjI=M}DQ8M8X;N6rQ(t|=1! zI%T?0&h}>rd&c!rf#GP0@LA9cvYH4m&`H!4}W*&Fq^7BX3&4tk2_n-P`Qc~`ZKMew*fC>|l zY5hdxT=>txn-6m8=My7&{-+`{D`P>$*OV+PS1QITkayaQdA0b^k64KjUtXmWN2X!! z@uicFyzJv}9a+S^ea3|Z-nn}FrLMj+2t00sG4YZ*>#onv$V2{v4(Wg@MicSHh@jzo zXesOAA@%uW?K3zmbcZ@b!-nK<8pRN1W+DWw zbFXy?@Ms5DbqOIaECPa^@?V zQ(qeS<55ruhZ0D<`|ewyDcS~mmaqJ;Mcl63YQO*fI##Xv@sNV%?V&kSG9v%)uh+R@ zDrhE@Wt?KXuV(j^6ykJ%vihB(007&Y!p&Q@YfMcDF9C|U~5e_JeoFA zBRE$Xk1>_oBoCL5LlgiiY#0(+h~qg3nB3fA2R8b)&aPX+j!>D{aMM{E%%46F>wJ|^ z=USnPg)WdqPFB2`Awf$2{zlAw$uLyvl3GHnRQS*GGNm5=nq27r!U5IHP(6x5hED!W;z9yj|Cd!bb$JXx8~1h(JgMd+ zD@RJ=So4BUN~T|W4WXHbXG939n>#Qm#g#EvF)mOuS*xTppI^IDS-MnL($}-pZN$$Q z;QV_|lKlk|+|z9$v4G^ZC4k#M`h~3b7d_hE-wkRxI!I|7hQVqzfz00hH#b{Z^%Uu4 zuUN7GZXzQF0z~W(amI*WP>r%8-JcLFJzE5$RgB|A`tJBT*79F5vCIR+M*XllU`u1; z+0sM#fyii+$2+URG*(VR%cQU0r^yW{O8lX?4^Mtpm(wFs#fcqHqX)f(@`krj(_{~S zxwhR!+M^HVTMtHv4~H)1Oi^8i(AYK1X4MevmgD>-&#AJHGL$AdpXqs)5PEGA(!uZV z9q^Z38i5sbGP^E!^8oP*bDuS|Z#?@&##OZIy(nXhJ?W;e z`?`8>lv2#@gFm+qoED2_KPgFlTg4bzb0PT+qPST%;}3hgRKArZ4U&sI#3c#Ak007= zJr`NE$ajaZ)h5#s^OIz18*0r^?tNr78g5s)EH%xv?A+_^=ebI^a;lS$gkE>dIz26r z;upiN=H4qCcfvpaxnL|^ck?u?$IDy(`#?rPQP|r0o^JM#Ffb~)b0&XZ8CAp%bOUo?6urm1jYGBEdwlT<^Fx2z(oP7 zc)W%>OEU2K{;_$7bK7(_wB@>UQ_7U&GQ-UJx9joo-4 z*&$N@9S zBMPKw{jf!-;^(;4xmgNuw(0UFf8f(2jHs^%#%k4~DNZZx-3XhI#1CxL_-)49M|8bt z^cBYFQ%QS0YNzaY^<-`wU3TF?bj>(N`H{$>4&xbUbire}yFA^mjiFWuuq+2eOQLw0 z1nVe9?in~5gWt(hmQrLfghVFqMMK{v(@hq1Qx}+;f8c2KT{j7pzQkLPQXkY6tDVf# zE5Ro^(E3G-uo7# zZk1)B@wLz8LGF$;9UG*O9$sAev!cp)o*;HPo2of3dl>=6?`3>tlEkWYbD8n@zibL5 zLD6c7gr2!bHnHNL-&oUz-kR*DJp4iG7q~|LiPAmT%2^t@YkeX zR{8wZAjS1b@9}@~w|CqgjKK)Q)q40}dGLV)|Ga;t1`DuKtCdb}BKZO^nAJ$O5W!bT zaO7UlV(W4qgqbP(wOcws72&nCkRBNcA%@7uS2G&c_p4@c9gFz5r9 zeo*!aPVcs4pR1>80P~iCfkCI+S5-q>8F5FkjT@tg2iF4fQ4X|Nu~e^IP)fU zBUZcR7etBXLrHoWTZFQ7r7CCn$qm6lUG#->!n2Vx*@Q$oTRfJXxp!EsdNql>Hk^hO zj7xFS^N9nW*39K3b41bgF3_P;lgKu1>t1dTuD(a8*mys=y+lkWHfcbN)D%f3#-Lq# zlR!o>H#L70_gjH}r8iWve6B!ckQPbfrF-BgmfOoUP4tImw~rnxsKVh>z{p*I){l=Q z_E}V=QWK4UiGFnX{uvab=~(r;Wl8IC!-cObM6D!2O`DOTD=+BF_{|ibtz$JxS<(T4 zp&bRkJ4M6CDcPz)h8qX^C>)(EJRTPN`I>BRckBUFCA#pd+~8cD{i0biNpC#+XIV3> z3)6KH$~XuwS>=Abwdd}RFCubajk-lrF7uC+ro82I{zpBC>N!qp%b=jKTc;+^d@MUL z;}8=do-4ZS!-OcMhc5QzoK6-mfdNgNKdGdOS?by?1fD;-L>jleE~FUW#e{N1JJFRU zn}e$(%e3z7>}wv;g?Ci@mz{Q5E8 zUb+wbGU`r#_=|j2%?0yz^+(jrM?J%9PGpgSGlTA*j8#}kMYC_o{-X8Jdiy++TRK03 z7{8h#zd&!(jz|5L6`3efM$8BCtoi}P>$YXndX|VLC`rJG`RrM(?M8&n)}(~Mm5Gu_ z8c+7`TgllLI>%+; z82?f2w?(bG-{krGcmMtjU$1iy`^K!I0Pjje;r)JYPhSZ9h{M9rNe`>Z2hYrk;b*IK z^s;K;LLvyY;n}@TFidW?Fe(3&mT;PVg1`KLv*xg_|KW6yeP!8`uUl({nVF2pxo}QcWl$Jj* zp&*(4eSb8CfHf3&^aeMFGEV$*%%b^_OB=6!guiXV0M6c}|IN4(1G%o0}k z;oOPEb6mvJY%_JR2(}imB<~HZ6a=LGEtLdM-hp0#$1vD^2G);HC))w?D7wOcUR4Ww zL7;~^kIOQ`z}mnW3NxrSKHlyT!!5u3p3JJ-0rR|eM?JskQ~u=g4vxAFmhuI>S=gc@ z2J~scQ0B0c9Qf1H4vWp50-Gzoox}5bqmQ!|K##K2bUuIMQlVJ;n+Nn zD9-IZ8g! z$Bg5?5gz<;SAf1o!{B=q4=Kez=|&B@lrvJQ%Fl2cKHPSk`JH#XH#3CSVxLci3XZJ8 ze=`GfeI0!`o?HB9D(YamVnMgW*dpWL_A^(cv|A2L3Bi$`GjL<$XZH9Af;r}W?JZQi z%0gzZx;x2I9_`ActZ4rZMmYJSJ0)w)XTK{>*D4KvXIxjPS^2ujj?2t!p6MDrW9rKU z|BY`PFW$NULSmiH^KXiCq5cP6dPJ8rh2q6zhy@Adt=&})XAFnUTdgzoJEXr-)))Q` zi#9J7EGj_wPuEZ3rKBq;3|Iu2AxNGb@h=IFlX94!BTL!&jz~ZxXdu=MM9TU=XbWu5M(BYW&fO%kwfy|Lc+5xHn6c26lBb+zo|u}_va@E9 zk+c1k$TtNnnxrzO5O_RGgkI{^&tEX>c}IEwrCiF{$@}aS+055FExFI{L5+UDA22Lc zo!725x|^#sbhafR;;Gc&|qKYYE`&ic7>`2(Q<W3aUS&j3oocNKJOnr-*EGJV0KfW ze`w2=_Du1#vQecr**YiT<-|mk;F`u?v;o|Q2h?Vv%I%^J&)K`ZH|BjFe@csgzdL@d zkB}IDes%B^ApUX;Q~7N%vZM@6??&-UyJ>sU&TQ}NWvb6q>(6#hpFxY4$Iugg9+H@T z_!e^_)p6g5?2RrqbwG{O#JmY#isHf=6tTCN!x%#cUv1C6qgt>-2jZ7 zsAcESHu8G|HbPuIYDqtR#47P#ll^Ha~x87v5n#|S+O@9SwE^xS-Mq~E^jcYxy z0u->?1@P_F{n`;rFq2UKZB|!v!X~VTFM!0UZ zl>K5;+J6wW51m*|-~}y=jX#4EV+<;lZ!8mk18OS_KR#Py!D`SD8-hXX0^r@Ju%8K# z4bia17uain(*{k*AN4fbLDI0PBE%HIjkq&J1o~bd^bKmqx6Fj{p1D03^M=%~ zaXSXa^`UQ4dGmBDMrt&x4=1{4234jX3A74Y(&457DuFKA zJl)>bSn6U?;*j7(YIC0z-S|oz1c&0O9<@r%n@OQo^8@@CQ@3%3M4~L~c&4*(RssYZ z6eH#;YUgVm1nvc|n1Jlp3mb6s$TqeB2xG#AOuuu)LR-+alX&u7EWW7W?{a3FG48l( zsn(ZtXP*bU@&?J~v2TVYuQ~bUUdqNbvIn1%+O>1hGG}-wzkaLRMTg6q0QH+tx=%u9 zB+&mUGJ+p-A5o6IC|2?-X$^Jjb!D-PHna2)TK(JZRP(@I+ej{?0BfpzG?(|t_4|um z(cv`dT5A~6)U4HSNs4hCS!{H2-9sspPMzlv?T!m_ae|R@#hGHfZLPaG*`4dsnWY2- z8JV_xJ49E?Xl(g*6~oYl7B4qJLkZQxkiYdc;4@cb#E-mY8}~jU^}LgAekiZXd8hI4 zM?E!X;bt}ao-`%M^^4gizVGtxjze`A#y#}KImZ$GO+VWVbcFIlb5Z$KfT23N*BtZp z)6N6Z^b^hE^I_@1ET0*h3~&0o0Z#qOs!-{db#uv>sQPm{^+zP>qzwW+>iSZ;f3*+W zTJ4RQdbci9UyD?^ll$z-u?t5YV;e>z94{;7NtDA0&M}A%^MRH05a-#JS~&NHX>X>Q zrEZ@2WV+=oNHm{_!{>+M&sz)E|*=t`qhQd+uAVz$Rc^Wr7lactr&}xILo5y>(g~Jlz<$5@>w--Fo-i z&(H`Aj8lLQJF*Yo$2xcIj9UI6+=#@)>RrwRxWQXDeuEmIC zKX+=1gUMe}&v|OVNf0(Qd`_E^yAwJ)VKv)u6b*h=Yj$Q( z$)rp&V*ds1wQxrqGh7t?B=eM>xCnJDWkBU|NLvxwMITF-c$~O0c1FEp(Rk)N??9lyHqCNH{gQjLLzVP#-?@(F3Lm#;cH0TU6 zn==yNE`yY<65KBxTSDLQO8!PO`$fvg*tAGkc8ejf*7AKl^3i=+jpJ~UV^=(9x$|o- zT?yeR+hm{M-W4)uJjz#;V4n)n!h&{%Rqk|~rX2m4q^ zZ20P=Fh&XQwB^MS+AoPNQIE8M$Jap*=t~K8coMW@%iIr*tGE%mU5zQo3hdCmO_=feKLw- zqG=g(IYTiqve;0qAGOK0b~p%Sgw_e|h6yrAM9O+DEy~MinZZaobc!cgFQT|&DVb!o znV8oRKO}R9(&2(7OX<>$$Nfr}h$PA8W9L%rpwnTF4HDN{4FAL5G*m%+{DPU{5E80x#Q zs*TBMgvL77-DI!(H*Vd3^-cHlE$nkurluxy@ey@a-6p40l{~iAPu{3otT{YG1NOU% zoc}KK3~JrU{rV$1pFP1f1!=)iN@?zuKK`XeeFBrkb1S7`ULpR&o5a$7$vMITqy`K=49d2G_<+PC}plVi*6di^tuTyTgo=E7z@Ius3K$ ztmXK%{Q^ck1gM|;&va=w?U%5Ysbn^N{-;mJ?WkA3_ZJrzY4aX4)G+n!U`7G&-4fV2 ztgla&8UwR&rzqpPdFf6FSo5;CoMCOSeIG^q0jAO?0@nitJPKn-?ROO|KUiCYS4F zEy<%`i1XTHK5Z1dp$?0EmcWq84e!v4OyhU9#i>DBSVCJi@%M*j-4rQ_JXRV zHQ{Q3UT5AXh~ch84&=~2q69O=A7)tn?u5G{N7mo1tE<#DQ^)%@se!| zG*0o)i3gwYXW>!w?Bcm4`&mOgCPJ;Q4;sQO&t=#zqi}x=VCNwNFnextrsye4gAd`Ps`3*TQqn#?RN+pP%x%`>y~Hh6bhxfEi1APML2 zB=Q(05}YY=bnG}kL(=!;@$%OH35Q{i1B>1W0W-p)vh!|vGHvcCvlgdN8=Irh_hxzY zEcy{Ac3N$^|9A$ed}`nfnRBknZ|l4`?}Eup#7(zmpt1fkjS|pF!FAVsF5M!xpo?o8uAo4-(*Zdxpe01?-;7w+Y z%WNd&M+#61ax#Mb$9KKE^{_7gq2jW*v-wx@eoDhDF#3B#;1l934V9hA=$16Uddi?Q z?NL+~$1Zt{3mcZy_;_X#_Hbn3K0chU;0`}ND04Y23R>FLTkiEvKVLjXX}RCvs5R*f zMKl}$yD6}GD;l#_xNDwD*DJ4I3Zqv4X4)Qz%;oK+$&xhu=P^kGSab%tMtu9i473fL zk*5uP*ubR98b$6Uh@8?>624it5dr%=a7n`8Xlnbt^x*iolk*7r^gIK(hIxZvI#?~^ zu&SX?PVwE@g(Fn&9vJvu0zC@>qiPuDih{M<>BGM#mhT$!w16cx^>pL(?i z*d((3$}e*2+AviJ4JU0?vA3jt>FvF|d$P+edBCac#FvI7W5k?*$3{9Cmk>dTd|C}M z!g#vCj}Z>+z4+3cUOQ%W?dz_BjgBuxn%6$D;J-nDhlG5@DMn$Y2s-oTJ&VNOK48l& zkglEyITrkN<|VjA@;v+7BzNMk2qtD5jSTMRiVSwRYn_@u@X-vH)wk6&7#Vl;>fn~N zW$1ffbCM6EWpW*$g@O*!<(r0g`B}8xI4JNTrg5g#Gbch!1n8dGs9%)ykF&)Zx>5) zlI}C|=4Q%nC1ZssHD+n{BU-!0{aQmh_v!;8^EFJH_^QV=sNjbuC(q@_No$7>ug{*w z4yLTE*aEgzm_s<|Y-kyxOiy#AnS&eO)1!FHCH^8{CL0O%q|+%sDJ)1#aVtR(*dt6- z>?4#T2*N2PP_>td4^~U<;m0;SUPkEovn9_~;i~zm;J>{{=IOvJ zf!;o%R>cT%vQ1uz4yZf%b1nYhdbT8G6ZL7^WH9UMaA$hUxareY6m2^TjMa0w7xxl2)Zc8A3B*b%Z01+yVF6V=VXRF%(PH45^W?_Or(MLPJ z$QMX8yeC)wF<(c7UBYM7yu#eP!WI#aw|Ub(V#SPUvUASKq|PT5lN|10++0`4PKCm! zRO$={dimYG*LoSDdp4Q?x=6RysHoAJ)a*_!?sk3e)pLYQrH)rWS2p)kE+dlnpU6e| zvKtpr)*36>P;D%cZJlI$WXe0DNN&u$OB$<2Q!;2Vm%8@2>!i(e9b&!DCc|DgjxgElfuh(EY;a%m*bc|0%HZT<96w?1+({}ZORz{brmlD`ubwB)IEzh>aS z-6nV>SB7;&Wbwe(b7%;tS1Nl#lPJ7};%>vvPY+k%hBV!@-Yi?f`u7y(dJsDD!Unq4 zl}?p0Jh*!Z!;Vv=R8%=rj=mrdbPNVk9Bf;W36_!&8m5#1SRWqD@LbC{rmEvieg^E- zNlX#ffMghLKXUZ6Tlp9<-Q3IAY*={)>ct~LMGjBvjbcU9X}?X+Qy1JZD9Yi7`6rnP z(82EJH9vnptehM+C>V3Pgzk$`6V;Ef{fTizNygWNi$^hs&Cz>kP?}2epY|!WIDw{z# zusKPyRIA!;M=g?55NLU@Mo^ez^fqg@F4On4@1OTxdDdI2rK>AlVrKdwOZ+Wz*x?aG zJ4!2CfcE-|ZSt<`4IiJZwl?usBDPhqWv~Q|UuExqCttNbJ;1*GPVo`txNKN z9VoigzR2rGVk*9>@d;FGj64!+o-_BYlS?nqC`B7eM}dF;Kw17>tTekuhBxq48($Xm zA3waBxuKg>0*32snTmedXN}MBiN#DQatF)UF{ah2P$hw|BMXOC^_mgPnl#ql4l;%G z`pHlZ%9e=e{&BvrS|Sh{#}5swukqPJ3H5NWGWW%}pq4J=7xTD!(ezD*0V7YV_gkK1R$tbLMudJxPqsFuED4{(LG{Ye9+wp{f3<^hGcIlKyOoCFhn1zBgI z<4PzqRENoOIU5{+5K&6_ar% zzpHO;pq-})=jp0~*B*~kpzgWfyz*7S-ACbh3eWz#)s&=n+3x(H1%~?e_8G0O*A2QT z4brLgGkDxeHso(dUL5}Zw2(kdoHwh9W42#ITq7m~p9~GKkG|LJ3#FV*(L$b{p_U8% zZBl}q2qeMt8=qka=rmbV>Pzji?!SyXyAq04#?-vI8O6!D{e#Lxbb1%&U!ICJ;CD>SWW(E5FJfh9BU&xzyxI_L-X?k6mTXlt;~{2pz^o z+x+~(eb;~J7|D4sp1}DlI(e?ql~Gyn1PQQ@o;va*zw!bL25i8%PiopPwPaC4WF``c z4Lt*TD19Pk8*Vl;yg;iC-%de7YHI2-0H0`~M#6!aatd~j zF)&%Xy}dp9$8jXr@G{K91v83(@|-S`&a0Lyp~qE@$H0w&lnHC@nJN>Bty<{RooL2h zWox$2QF96yS-`;ob6n^`Z3&4DK8T@Kz)n7}A}=tRMwgaGz_M}%q`s7eE(jV8qSZ?N zfZ~9DpsU$D0s>Q{x)NUqY{yXPcVGvzaDzUds*eYHMC^mV5GXyD;X(aZ=d4vulxuVt zrMGqW+p*c(1#KK87?}lixfARGGw(5bCBVI9h>=E<#-? zO}KTT&ITW5sS|lTiSZioS?uK7%KYyO=#;QkcmVu2S6%rZnQ|XoD-a{?ExV^kS>@?v z%V{FkST#D#!y6wE5k329mAIWX=*0QAFvxzr*&=^$L_|vLP6~ zz~rR~hjyJ2y50@I!n5jwsOLjFw7nZAL2wRj>hTNuJtWU7mYHK*JU|-=tr`B`J=`cg z1@ZvS^u>mvLNLut88f|;sB z@HFIwA>VS0xE}9hRP5}he|H7$!=_p7z^!duZf-vP_N~y* z}0GCmKhY8uD!%ZXsMVs-EPj^ ziJ3z2s(2Yy$7v(p$o|-QM!4aU&0DTY`n*e4Ub*q}IZ}AdUue;|J&mb+A5#H0f!qQU zhq8`Dn**}>mL zP|Nx}<|+5W;&+w-dchke=DrYJCV5&;W14dzHHT4tJM7^wgspFBqHKNfUz3))dMbY> z)oM4}Tvk9m3YjmpOR*g{voYf2jsEIcVD9{UTfWYHqw<8D z`5%Td+uX}D;|WY-{JdHq_PQkX+-jw)&S7*5BhWeAz;+!SRrz8g`~QeK%b+@}aX|!*nBAs?U}|Qg3&$ z9L_kNA-l%z0?pf%@~>gxTj3b@wvCG5Vpd!hDaXE!E)0{~B$&=YOUv$dD|G7%_swKn zXa9d2+<;K!Tm8`G7|v8*_ew@pVPy)d!6a_?!=R2p3RhPnA~u?5gg zPMxKbCYD3kw;j`bv|Swl5W@7y;OEEOw?{4mkz%a(J*dakD6MQl5Ks)c9GC(bmhE@| z$ap)HR0=!_4E6nJJ#^1gdkfYE~cUc9t_ z<%`24I$r4HA|rlk*axw^&$_@slauA`w#M%zip~B94y*q7&R`^}9Sb?NTq=txpsIiJ zncAo!1|af8z$xuuldr8*8T`X|SCOO|_9`Pk$0`9I$-I#R1Z&3y6e2DQAjASxHjrt;enOcaZK3L?(WkEQNJtIm<|skHY=*%4h3UXGRyL@Wbn7L#0W!OLOecnX1N@MV>yvH9gLHIrt^=( zgS*x28_|{2{At2TzNAq}I{hh@s)Xkm6}tcrC0U*XSL&!~q!2+% zO*1M&%oAZ0)40*+5uryeZDZWg^8tBQwy_RN&a{tB0t`$k$pu*(5Uoia$VE<)=&X#K z!35G0DoBGqhmUF(Q~2N64J#?)GQO;Rx_EX{0>mJMQr1O`9LwEtTFUCsl*&rd>)#Dbc90>w zgo>DPeWt`@V$!wH?|qD9o#ZoP8G1DT!R1QZwiSz+2opdq*(oVqmnI#fVL_wNr-xFC zS=p$rAfJSn5kI(uu`TyaG^hS^iB4?X>h`{zmIk4@B{5CcbV-l@JEiZ7;;GKoMLZ?w5#{OZGAo>zJvZriiSM@Z4${E;^#7G~_iG9M<|Geec+CS0>z|FGsKj zbN$Z%a1j8$8U03o1L4l>PuU$u{t+YGxy1!!@Q=0b<)mHMOUbTwO+_c zE-^Y)vaMQ%*AB@haP61#;}`aOX^$O;^Gn2NulOd~FI$*dUW@c_L4KIPljo!*BX<2^ zDYW=>sWakJ*ms?t-X-^9gq}020r>B~pL)6%q`pnGZ}^{|S0oXUR4G?2@SZJ80=3`y z8{+{nq+bE>09x^!@p0-6Oh1qA+Ek%}5uKLZ{!N+(INu(9=K>*t*&0xMoki6EC4{}y zg8@S_%Fl5zCg{w;M+vJd~gEPmhWzN@(2(<#J2z^s7u?O~XIDZcyd zsrwMPUQta=EDR#f1+XX0ktXp70y=SM{y<_)E?vaW_b1xs8qmg11RVJp+}36IG3D1(7en{~n|Wm$l#` z;L=qn!ossv4iM1^#f~vfGcQ`SVZ5Z$(ajp0|BMLh$z#3%e=~KOZ>}iZDh5eU1sjGb zU6{=AQ9u^YjKo`&p@7cFyHc!*V8#M3kW8Wsx||9VQzMRk&^Vq4e4&w|Y@I!zEw>&K z2l(`u@^sXUH6xAxf&*LEs8cVlTCG}l*OeW^hhy7@x0lYVp^L{GxeL72b zgjm^+9sRDWzR@4PQ(Q15Qm%Qpp7uu}+tXT^zHCJv5KFTky1Smh{wkJ~nBd#p&9u;{ z7*L3#pi&UGY8pSUZEuIG;4Z_f^_sDWc7dfQvL^BLe>#6cPW}bGxEk{hHNID$p1O(- z5?T!QfdotJh~t6aGL{aC00QHI8m& zE6l(@bWzXXiy$ZWC1UK$7Etx#MVi~)FfOvTfy z?TCfc`)KSd=Rw^q|Iec+Q@Hi}dwcx;?B;~XDg7As6hwW$RrF1vY^l%(YY&Uq*Rk7n z0rfRDm2!*_@^%`^lry+j!RCg(nBG=BhgoCT!!v_&bXxlEpC`U7?R&^@H;GwgO+WWv zar|y@b9rrY{Azx{JS=!Z0M1cvgh!%8apV}nqa2(EcJ^a z&Cm3{8PQwE!W0asSPvAH_Y%mV1#x9E_pudiSg;G}6lyBO&5DDgT~f+SMe!o2V|iSu zCSVPF3DUqC=w!hXI--@B5=TZ^qM2CqR1-XX@B#IK#`z*bxwHyuL2;+9`g|9x3Tm@l zQl;#waZNa-@WEn`%#!9kXRDUolRA|yc+g#3RMCzftNgNTbr%yGt%__P!lLmAR%Hrx z`b#J!s>pYSpaYa;grN`7X%oyCO{S)vQTvb$~F$#r> zf>tvY%`$4psuccGg|eOLXcVKVh|pa{XnS-I(V3i7PcG;)} z0!A#Ha87tILy@Y*HYJSvM9@Cs_4xTOwGnObV4M`A*-JC}kVN1eGf!mQ*V8@!bH<=j z8P!@v<6u$iU~`z9Mhi+#ylP_%+sh(}A2y_xh_Y6-IF+(%amocvRHa{WoD=XUOhAID zjA|L>MfkyFFxPPUQ-1BogiQL*>wh(zg-uEPuJSIU`RrD>E>fN^K3Z_y7t3)@Ec8RX zeeXsMoo4KlME@7lUW+Oq;6to0H4=JsEcNBpbv^=f%MA;`WMb^|HGWm>KVbBP0r~g% zi~TmgxU+OGJei!T?;z1Kg^FS;_nLpF z_436X!fxf`Ku52TtELW9j&4q!&r^6lL4__mDX$xhljmKo#fpujz{~h`$0_q63Erzl zrnjw2qVVh36iJh9_fA}IH^ZZMOuUY~0q;G_ zh;jg3&ig^ky$??g@+PH+hMLigG<_LoIGGyMmdl`Vx7FgoCJMZ8us{kSJKP24=o2{6 zJOwjl=KfURkwXNo3}jVv71g*Bc2$`m)%1D^9voJ@uaEVRo}Q6Nle%Gt`;hVU&lqw| z@p%g1BSvrGymYRx9S|+iEijeEL{}Nxg2O4Nnxs!Uyby@hoGZ{Jh@=UpM0kX-wK;HG z4)GeY?C|y{g-zd?#;Y8Yu_h=DY9#`DfXL%j$w#sD)qVkFz=kke^Ee9YT*ZVmJ-oBx zXD2TK+{*r+l?v+1L6>#puC^X2w4`6en>ZpIpt1>|**H2#M2U%|)pauC`cx(l;oH4u zBPeGBJsHVC9|Wc-75_+BMt_@;ZZ@h~B-od2wq5%rj#_Q>GN&|C_SvzS*puwe>O(Fc zuQzG{HL3dz4pt=W!SwHBX@+3s1g;6y1o8cNo#C&YD5`+axlfca29J=iltUY)TqY4P z?GQ|AfrABtvx@Kzjz$khW#yRd3~foQjpV3}UK|Fh!*ReRTqhcpiUCLjCYV|5pv_^1 zV0Lzd;pZ&VJS^1_@$xM-YZ{wX?nxFk>07K3UbfX)Fh--xgDQn!EKd^;qY6WQu?DPg zG7y_0g|UZB1CtL8Hhp=lYv$Qz8_F*d*{Ri3w$>H{E-TGBF-ckO9VO?`Hyt5D&f|KS z`M8(+f_Z&N%;`nFYCuhY*9C_fffU~R$^_|#AR^d6@>KDJn!(~h+D703>g4!)0s6^J#@m}WG)2zLmUsQgMFAJQ_rDw=*K!x2Kftf;5E9mZBKe+sd?7v!o zQDI>;QuK#I{nO2^UncDO<*H13_~nC0cUa|7^CkVlln=lJG$;eI?TmngHpUomkFrrd zKV6FVXWjQ|KYaTCFP82P5J!xt)L+*)sjbZw=CG#5wz3?jEx0(2=I+`#bxg7;6^K>V z#b6?8<4TjI8h6sD6xdd$H|OMfQEMlSit{QeQw!^d+J~BC`gA{|BzLgB(L|HZdNWL8RlkVw$yA8)*kuv&y1F;5` z(glmSDGwg{=^?WoG?^VdLcXk#1oyi1w{EGe%6z`&OqW=po`0pJ+rbJaXw|2=)X-q~ zSvpvXsBDQ+c;0Rqa#jl(mmd>hQc1xMH-NF}i6nZFXLab|kxdP+8QXVv*F@m%sDJ&w z!pribl|Qr|p>c#Fe}i>*;6hMNF=I`d+9dn*X7J|SDCeY6u!OEO8MI?oUe(}dlKh*C zAf<*CpjTF)2ZYuWkVJ9vJNn_53*uiYl_PsNh2nqk_${F$kS%wOe%tI+%qq)N} z(DB5vS}r;vPM|Rqeo&%8J_yOK_7yWHsqgtnx6&Q61gmGVPoZioOJ=9!a?s+&IYG9O zhtE|?>e0!*4@KR<=%4DzQnGThaZy4qm=@{e8R!nEF{FPmmp7kIqs?dr@%=&9+Rpb5 zzyyq-_dfD-JKmvjkQQTC`>9U-evg3GtpU^jgQ9b&t7xaAGM03XA4=mK!Vx-0i*#7X|IC$@{#;(qs`KdD3=;>bz8Dqd4aBmMUI(LOlEM(i3^dYEEf7V1G- z7>Lu8RnZC2!Q?fkAh$DQb^JulD(=cXU;4vr62=MKtRp0LwrgFPCI_S|PILT32t9tG z5X(Ap#X9DOcBK46gg0*cqi`EY7d3q;UA*2qKRyB5t3k)Uxha9E_r(DKD(ic@1#BLd zY2ThyNFOe(P){!0qdE1L#w?i4wzFm%;b4gOH@!Bv3c35Lf13JQKmiR*~C%ge>e&l$Ymn_;?b-35?7aHMcV3sG{R2^47CW3`Jw6@52hq3Y{@g zD6-=a#hfc;oe_qbw@HA+%K(|vS{B`|B#Z?+ljcRVVRlC%%Ak|7qo_qZlduAu+3V?5 z!(^CDg$T7>kw68LW?SEwrdZ*DJ7z*OEj?ZBG(oLqEyBVHs=7Kk2YCFs|v;TiX(`wTf||q z)wIY@-h(>*H#&y6LS!px{+<*bb1i6&D^rV=(yYZU79_>03eiN@wFNifbjS4OSykmMt|0oD`M99D#{{Dp)3lkkj-_3 zRe7oGzJ~y?s3Ww@VOIU)$SOa;)S>g>V?2<3jcP)fN`yu*wuO)2)!X4Q9fbS29rBi0 zUA?iwI4u`Y%B+i;K`@%kcqbCExgNXLwwu>Ou8QV;lz}4P?ZRjybKZsPbl91_f6fZ; zCdHP9c)9V2GLK*SGP&w=k0_@PuZPKc?-cWT%BN#p+l{+VtW{nfz5i+I@0J2BvlXH$ zOeF3pB7QXrR5(HQ?ciT@eM9YV*td@(afw8uiQq$Q~k z?~E|!>Sz+dBogN$MJ;9aw}wE^{;!KjgX-;smMpn5kzd_biEsDh6Vmqo>_aE6&-cXy zF0F8Db649;KK=H{jrSaX&e>_So{g~@;4~V6+Z5z{UgPDG$|wOk6o-CCHK^7MyZ>7_ z{il3_gd{_$WS?85F3}-sn)KHIigfNaOd80qe|VLqzZ{9l$abz2VYf`{sQto4U(A0V zht`*oB@@I!nl$lt#wsQL{Tl{&@r-YYum%KnwSI^8MJM+e8j>0fCRfQyxE7x^i~K^l zTek|Hz6c8Qc&;&kN;1kE;%%%^}OA_VdBM-HhH zZ-a0{Xz?+S3&=$2zBiNH(v=*g_8=ss+7`~FG@eG6RrP=&OQMaUi&oI+^665M!3bID zP(`QCHh0c;eEG_%^&hK@H^lKG<4|J#6P3DBVtsGA)tFMuYlPr9w}00j=b0K^ zxunbfs8FBYe0&vUJezN45ohtdwOy`Wp?Z1kHp$di7`P zSJ)zHAgX974$8s8JuBzz$~Qo%D*5X948fl7IBoK?h;{z~ZnM*DgkT=9iiYCMv$!aq zzcY;=KkTLuU7o7xJ6Pblmu;V;UqEE>4Vg?NpMI^0dLXR5(G7;g6XcZX3yBdU&!)2NDK2S~qOKMi>hzixZ zi%n~=?3N3Nd8*S4$zUpElq7i_PFoB_eYO&KdS)rWD_W4K!ox)XNRpGheOa-Ku+&(9 zSCMgsMe-44U!ZZpJ%B6*StaS~tIa9}vHK%LlG3adRDx!wjSi*-^V~>EJc% zX}V@U!yCDnB2^K%$Sp8s{pRNv!x14?M1@ zl(z7YP@3Tlk@Upuex{1RZiR3;VvtS1V1P!EHr7(fzq(K>CdqIn>mEY=Gavacd#c;qk}&KKH=MXE5xalLGU_A9jJycii@ z>qG}oOrQV~V`mih>~|!Idgy+kB%c>h1R0D9(j-WNiY<6RCyWs?a6osu?eAl#vJ5;J z6bE_Y(;918h#AXGfKH?eLf8!>gm2ln(%-lPbWE}g-_sArkC0viLZ_I@sx{(X5*VDO zaP#J#a>6bKc3JH7*}=|B*;6m-rRH?6H-2bCvDr5SW4+Q41)!Jy8tF~%IqKxwdp*%~ zr?bjjw>ppYYx)lk`@hh4)V|hzY=?2m&a(bg#j`EQSiJo6a>b^&=9ItnaPyvF%<0VU zsJ&ymspZT3{ozy{(u?tL8{B$0Lr_5J9kKCA%5ButEnf8y7sF-3#W`Z)ocF~SH`Vei zzQUTh(>k|JBfHN1A=B;6aqm>&XQ`ofzvJC`tENc)LxJJpDOV;G5}~0HhwOZ*==-ii zWLhD?o@GoW$Nnb%lq^n^Sf%YOlk1`4?M?Hu%&jLl)9w$*T+<@&Kd8xC4Z)X{*Vf0X z)=`u#u(W*z^h08-yj9#X;SDGz^1|0FaV7NP$qZd=NS{w0>?V~9MY+LY1z|o~_2mxkw% zDWDsAtRLW(O{gDeG0<#R#$YN23?B4I+KZH9p6YW+o>hu9b0u_08@y4B$eJA!E8T~V zBz}8BfhX~Y_=_upA_d;au}KMLPB{m9q2b{q+AJWMtk2MSBkgq1lqLwtu8w#N+!NEa zJvh{5SR$P{2}SIkm61-Sexl`AUFQVFXt~qB(eW2r~(C`lyO54Q2sd%ilFREXXnSQc; z5W@%r;xS$T&O_ zFt}uYom&&)@%J;wglJw15`3Dnp=dXvElAjjjoN*E&HXoHz{8HGx1-d3DvdtHUV96d zL;fcG=4E;37_r0)$wzcg+iXMOdp?zK;C)Sv_=Y1g(<*TIS3!h@CC-b48iGT3lZJ6z zc1^bvOlL!?fU8hH7|i?{caW)iKZ-)>QGRc)@-@WLvXj@i_{Nx zak-vuIMj)&GF9(~L%+XV7d2eZ%XKavn*NkOF(`iGe?ERm$$i=yX5oAO&Pfl6Gqt@! z`)4{HK7@cfgrI5AL+0)c`eNYBXRJ^7oH+ASwKm49&s`V1{@vmmhri$d-QdNL5E`2+ zpTVu5tEf7ybF_F7>6slJh8UB^nA*t%D)wM{G_h&R7RSW|3HEs9`JE&>(5z1KmOy+A zkHZmYLNjwkGqO)xA`T7OdZ|Vl+=PcF4OSr~pytdtUO{Qk5t3+E+fwMwSSYml-JGmb ztZA0|Akvb#>z$sgv4;~NEtx|V9_}KqhaFXAm)9uv6M;`wBnhPp489UD_jS$b)5$_A zl9kUWvB;PGx;|x*2xL1gfRm@E(hz%hv%UnC_TE1&v;r%qOx+s5s~BHpm6yXu)wyE{ ziB#~8jSqI?Ex5Ajf2;i1dv zEBjYDGF2Evu_QtCbXoZcMI*1b+||w`c3f6bQAp&)WUL5MQla=imdZ-7LqdLl=ttXm zt6H`BuCLv#FGX*^lq1f%3|6AnOV*$n#3dkUqe@GL_RTxHvuK3!Wr@qe5Kmn z&~_jkG26!W7x*VVko#w|)F3pr;3FVQF2l1iP{w12N*XK)O6Z&Y^d0?StFD?!5LXqw zh=x%W4V^E-!i7or=KR&o_pfkQ=#h77t>Dx?EluxgV&00X-__6w1Y7PS98a}IZ+W<$ zh@O{5y34&@;PttHBH%|(mZC1@6Hb8*{Da++xc-8&M{Rsl_xGvMeDm6NdpNk^!?$gU z)O{g8P@WdsHJ{>;$DP7>gAx2g%Yy*@RR_hEq0CwgBAi{h`G*MAex{PpnN}>m}LnCHCT7&=lBn~T?XjU5eNAsEUtE5$sE-Ra3wcZI&}<|$KuZN4CVOL zgfb9h=gg^-{V@o`l^J?n6x4`Pge|KRk7s}M71A7Wher$~i$Px$HMZ4dAeJ?dQOz7D z7B|Gc!50KfGLx;uI`vCct2p>KzfQDq(~)0FiqPy+_;#)I@AGQ6M`DYk{doEICs1Zd z1)PwH0~LIaWBv`Gs*KDwQpfy&KK~^Y_KTu0EGB&rp;q5WfJZ=8#`3K`lH69Z=H}e_NOo&OuBt_9_xEN&x!6BRuz)(DXV(^rL zh}QA?1_42WFMR##m-%V;{&d*ykk2v5_607oCgIz2DjN-$ykly&4Tgf8j-suDgMFOwZSBf$Bh?)`F{qlS$C4 z=nAF#?T<)%LuVGWe_W(Hz9Eku{Hu@IZyn~%D+M|V>1k8i+cFADg-l^L8_}xpiC9`O;|Go!i#3b?AFp_*QapZk+c>DNS3hsVWshIM&x)+|`X5 zt%L!x(dqUKuS4|y*JZMP*K9!C(;WayV38MDxw4`Skg8I&y6L2(rM2sf2VuNU0j3oS z5syd$)w|LEdoSM6EVTa*`y9Zi!52!oB`Ffb!E@zvxLFTf`8T-PWEflEbpUhEL(w51 zU+4R(ayHWmg`@f(ZUJR2IgQeBx3=XiSqAK8yR~2ao4!G~wge}sbDrJ~GNg2s=FO*R z=UOPFB<5D0IN?R?9|xLkb+sB?nc+}~%*;w;YYrW7teOzO^zue!H8Pg-)>^wFh(9RN zqw~`y^?IWLd_p0rFxa+PE&qA?hL5ye0ySNSfQ2gU z+>DE#H~6DFWUu54$@f43)-ms51gg3oLTM^uutb_z;(m;bXo3Yo;x>#7exU|DDxx{N zCYrTU6UAeC?t`wr;}fb-4Ozb=j6WP|wxc)8VpXsAKT<{Ojyj0FJ>r%ER~upT2lK86 z({o5dEg&($53LqOw#$Cp>c{!QQqWKNE)YK127fSW$UcO^~V-e0O7 zzEP;pa`P7f2RM;bVH>OoPgFPdwbyJ0UFQgV8gM+;*)$+i#uLvN_}VH;ubn}t!H8$= zIEm>^7QyYIf0l|1OaAM;THup9BYd`9><%v*Cz>!^5Zdjlhllhfj@C))VheC;gW{ zpPc{$6{KO+e$Csz8c6p;{!Q5gDs%5~vxd=&iwoJc!^1;-0?)f)fanakC=TCafgvDJ zoR58_aD>pb<1vFotb&sgVAz0n58iiGg!2!d4LTDGrDIjfi;7#t<0|!d!L~(GrOfP; z37JsYMU^^r8Fmel?Q6Z%8H_EqCa!i}TxjK=;|jQ@K^umF#?7`CF3i%giVn`k7`srn zQo+o+pBg-GGiKw+t5uZ<_c}S|?A#l{ExK&=ZD1FmJ;SHYP=+KuklgdEo~lr|#P(@{ z>f~1Ivw@OX+t*2Q4GA5&c>e^16Q&8xNS>IB#dutp%ulxYYolqInbfTuy>|_0Vs!Zl zWQMi@+&JdQP~natmHbnG*RcQ)!1gvfpBR|~kx*@>V0#yG^LHd1r4?Z|w13#&5zUy4 z(R&c+YlCT_L&LzI%uH1EK$Uv*qWo>0c$pewC*c$^3^K<@E(gbxQ^0yHOe$ELh*Qn~ zYPou8^END!CG(>x&5C=-OS*kcd=$YE%ZP}MIm`vA*O0l$G8n8w?ygjuQs$>eEW6{0 zy~98=B?mzRP_8R@rJ11h#VS_JjK5`c%;i^yidfD5L;J-tpi5$MSlb+VPbL|&h+LGS zwML`rQY6DJ8M%en0iTxV*w*7Uf^gB7pWG8#pJi1&mrsizK;|9fghL7~xh<b;F8(%nT}yS@J0R}zn9JN40Mw`M0N25L$a?GD zRr$_==0C2p_r1>SIN+|)etrX}5%1=3U~w+NePEw1Mub2#`IV;O&r>ZOckf%rn)kPz zjIJD@@%EJ{5F50HF+UVL_*B1+usz1Z%_~ zq)M$%@-<81Gy$tJVmk5T@{MV{X_ur1E{FiW>hrSif~(GN`XrpNyf{Ac5fLitMZC67 z5=o=*L_Ff*q9%sV1}=hds6>Wyi_Bb^*Of#Oj*~{w`3W@chjdp_9V26gsxtC9Gsj6} zbtbSr;-8Q~ph(63i4su+(FkaC z2;b2nz)hkU!w(dNDVnmoX^Y;OT2>Uq`9*njL>qibN$vvr2!U|}db}@z0 zF?qpca|~rb zowq@s#}Bv@n*>s_Vh!o2V}U?{sal7l^tB)^bRjK)z%6W9sHO}NaHMLWp#TM4P*5LF zmfO5_gjVWGxGEjR7xF2lPP-6QGI*q=#Y;xvw@)2g|8!v+#+)JoiH(-dI2j;utUvF* z-d50_c?m2(=9rW%bzrm_z3^ryKK>Qn>3Y63rI@Ux@}C#J;kzMj`7$d-Iu=;(#A9D| ztDWgjHXv#0Kb4Bq^Tq3~&aq$Tquq*pL*{itwDt7M`L%V;iV92E$->|ZY3Ah0=ehL` z-ymK4RwGC6&4-XH=}6?0oC)PG5X8a{tNcIDw=teOZ<&R|k^-4Cb6f;{`mOEZwvt;m zJAT@plr#y{`2`j-+U@R=J{~zi`Kx5*nDiNCgdf&Hi z3ho{f@w-N~wzeiVf67#dAra*JS@z+JFT^xlY#ai%WF)>h;+B0rT=wh_rsMboDuw0$ zH3cnDNiqbW?Z|_gDxx)bWWU|Vlpv=z60GT ze>odZh5ozQ=v6CVLPW}8n9=Sq^WHDgnG4M`FK`Gkwm*M*S=o$Wu)JjMm`y+cS``CL z&*K*R@7fzzp?G1jEZ7LNv6Edems6VRG6wsXqhRS|_QWMl#K$6usw$=PiH(T=G@^q-vqpe zS$fWyG8X#=zLeDB)kQz&xcn3c7Z*6?FtYeJ1fU&ISF0$Mff>?jj0xXKi;q_ca&xE0 zv*S8y(%m9n03$PN9NXyOWi=h~^lFJOIcxJ{sK_#u#j{s($}194p1aD1hM32u2KoASMj(gZf^u36mzK(prM@bZ$UvtH^hh>zcPw}i8MwPq>L~ct3`w& z6GY;8SRKTSIz`aX2LmZs&(q0>ED4km#JXt>H&Bj12#53t0gE}{rob`cNtlEp(H!1` zj8k9Xp&+Za=-9U_gWBsqqQCB)P)fW6`+6^Zzx=kGx7j}T*%~i!q@Jnqy5;Td-Z>YF zd|U0Lg{)9>?EY4qAHMNgSO$FMmBan~eu`|ndh#MHB`o(>-X_GDiJU;h^+#`i!Cfc4 zPG4tPi^jZ~znMF76CS>oXQXC7RfJ9GKCyGlrI@Zi2Ubkt{T1pur}Mjt+uMD+ZBe9) z=PkYW?Bayz_c()mAUgkYNOXP5(qt9B`@BCjXf^aG4Ce06p-CWkyX@l7a1Qq$- zyZNOySpyRLepIz`^`8eB*6s)C<_|BMZ$3py?_8ru!%KBO=OeUsw{yV10k~tA>k6M8 zUB7)x_yC7;3rmw}3)t60b(|DXqDcc!ns>#< z_l;z&`z3FXQW@`t7oEwE>pKs|Dh2_a4rbAy}VBigr~;$ z(37xu4EXM>=pBTZU<(jMgM=rwppBDeC1jyh+4&css#Lps%la zwQI%=5qlH&dErn=N|wgre*m}5OII*;PnfY}F%IS+)H$dT@CpS$rMxc|HJ^LbAkqCpV**8x~)GDCC7?L(TXW0V30hvGtwwU;Ie9I9bFzgyRD;eTD2Z?i7`#g!Y6k^(QiKU+z<@T8yV4kHIGVq(3|`z)m5k zbb&%hmHvTzY?sDz`6+OBb)FJDHuttTO0ePG`fyiy_j z#+sZF)n9mNjm^R4TT0!G5z3_Va+jIA?8E(5JnvmB%Juh8Us}JOx52oy33gppNqf9l z+Znh-#wR8m-!1N`evhfNJ#FvsLgsx`6fpO#8gNR3_1LR{M=m7&z-+vzyr zOz<1L?u9%(@}7fTh#7}MF8a_V;T^L$1UbXerdY-cv@k5S>YBxLK0BMWk58F7x50Rf znJB<}TlK^5s!g%6^G+Q7fE|K_Rxfbq>cN66r|JV`xq;}*i`c~wc6iLpd`q}W!_<%O ztdZT7dN>6IC36m=qN_x}k}A%QlMr!5*d4e*TXA5gqw?XE>w!X^L}nI(;ENZZUtB@v z4G?2joAtPP5|#l`RARDiomuO2QcD9|AzTfKu)6L^QDF%QsaS%d8`l=L?2DK4b5AQz z^S)PoXneMnF4TtNdDv9t6tdW1U3KBJB6%3YGUnluhQEI#e_b@pB3B@g02|52&~CvT zQISVb%Ezl<@lt>S8=Tv1`Lcm-4cctNA^t=we*OCNB!_x&NHw?29D>fU3^}^LllI#x zJyeVcS%h?aeIRN;-`8bjLk>BjXbMR&h-N!&@xqT&0UL85sMg?W`tw~n1TrF-0Co%$ z0hJ=-PqKvYOXA2vMGo$6-NlQ; z9ozZeObGpPrk*kEH+*Z4Z}&p3`kmkHj6B>osaU)1cI&4ee|6EnZf}wM9^4VO`W~he zZv9=oMlzvvvGeWKFn`V(z9-&C?b?5ge%tpGv^N~(^~xE3eKv!La1u}k1#~KretlYu zCQ7raGu1E|MVf>gK70f>3_khYRGV`C)5#`LIu{QYONvgEt*M)_JQ86(< z;nVo{*}iK3J%^+FWv#mtgzC2i=j;CtF6{tBFo$)+Bqpw|DKj%FA8nRJDi#QkRz-02 zW>?K2^Uwl=-&k4Ns>H23mj!e{brzsNy6;xBSc|;htR1`lGPZo%ClEkaEd`;YOVhX3 zHS!V90vjFlqsDgiL^|4s<~V7h3nAT$|fP{BCCji{;h=@GKa@k8#d}wDpM-Xzq!q>KMu;90oo$SWza`QEUJ`a1>)OdUI~HtI5)Whi(jDdT?;Rke*% zx3VaPpZw11;7dYIr3xuV@ijt~`xla6tP{>5k*q+->;i`_#t0&hs}0dP|4)eYWISA) zfaoNWb8`29#Xu{K-6|0TQJqslMVZjCG5GX=icAb57rw58B!k+wWX%pxMm)6{Dvi$i?7eK-N=@z1T#m7u4C`Fy4>~rdiN*I z$sKuLPP{<=p#IPAOoY06r2<1N^3l_=Xl0tW$wDWa-5QEBss5y|?Fo7nH0Vy(E^Tgo?!fbn(}n z=M>&)m&J4luIC#Q039A@n<&W#>YXVJ{Ng3czz@J!D*64J`@7N*`pz|YKl?*b6RF_e z`NScFc3S{s_?=>M08rt%03IFTf7Z>v2h^}6j6<;77do#I5afatOn{C}nm)_Ght57_ ztyt6mo^Z=s&!Tv<+Yz0epNa?2P5;;?b?XcIfyN7PcYXE9v}=)Lj#=)!M}v#~cnP^! zqdW2MwgMhn9Sd`qT}YfLA!@UxzQ21AwU__h*Z=BOkEknceNwN$&*B_lE zM5=mZ>13h>Gb;(caP{B9Zn$T);E|W3Mi*F9y0gwO{JH;nMIA=PEcZj=c%#Av3^KgB z5 LqmrBIw6Q$}N>IxyHN|g}Y2tmOl{2^2Hv1>LeOd3fhN#q}nbVLI02UP z&h2p)HYXBCvI{+ecgWF^40w>}>{^c%o_D*z=9F0Fuxb9 zzBvi<*8>$St$Vlv5*veo-`9iYg`;ZOQ;ymJJHJi`gl|6%Tvx_-@z7$Txu8&Y;^K~c zv7;fI^dj*yM&)m|Czny+=d{t&HmKLTaeug9c>Gv1pJH9T{%xqnWm8G$I- zXNZ05IsXu;_t@o0_hUHXw9?u02H~OC-c<8uaZ`+D>DclSnzR{zCCQJ3p?`ahJUrq~ zCsMvQgP&R9%W;fSn%3W@cROw*mvUiWT^_DOVcfdkQdy7dHxE}oumvgKJx5Y%M&;-Q z>SKkNV9wcWG#&)?oI^hx5@mj@)V^|hNPjAAJ+12guL>eH9pF{}+I&6Tyy6FvSR%te z(fUba$!T^2YzsC%zP1rC&b-aX@ZB`<{=h~D;z?C4D~sLJ58dOJ-5y&Jwpo$H4! zd;$_0S&S(9qek5zv@-P{7GKQpH5nFWiGuuFaEh@+M|_o;a5!Hi{4(#)es=jmk#mVz zT_FYGT2PlSNI7oAxgb}WWrCT}O3|A_O7v^cN)tQ_#T?)ri5lK|NMeYy`&u#PQ#w1K z#6(o%=Ly(FdC162NyGA0MAdHi(0)ZFDz-c>MG0&oQ3+dCf zQe=~vB0=jNkxPoere)`rWKJ1>??D%d@I3%O=19i5pU3?o4;Gob zttw;D7*^^-!wAXL_g#Ew=}H7@4nM-4Zba;71;uRNT!9bg+YdPkr6ZGxtX;>Io|h~3 z>yc06hdI#_ssEmGmXi2uC;<4M_eQTU#QFM4Kf~c9Ni8!Tr(Pv%&)5j`0G^yvS3mb> zTj^o;T_@tjXw8|io>o?2!9j7Mi{4xh%9@T{N%Lo&VH)!^>*nQVkKfuo-G$GOl<3`e z;cuRz<5V4PxP}G1vG=puBHs=LoUUydEc+xF%_RkkGnkP7A5(7`)@HaZ4P!0t6u013 z9E!WU7I&B8?(XhR@j{D3ae{kscMnqBA;6pNbN2bZAMh&|S4h_V+-uFunt4C`ZNHt6 zTWk8m`9z_MiREz}1e_$x+IrWy#+=L?A{+A+xT>C%GNg9E--9fC3rghU9QLeeX-gc3+Vz$ z{clx=hK5@InWgr?3qcMN{6|>M>F~tBrbXIYaH<+J-+*m3rWkV{Lr`DZ`7i2e1~PQH z6mEtA)pXR;-=PZHZ5||TTnY-r7Ct^Pzm~*d>SLCh93a6>iUoep7}=paLl}%&2}xho zT=p>7``V#o(q>C-R=<=d-TDwrdY8_|k>#14I1$N8=+6Am#u&tOCREX76W%D|C@j$o z8Aa`dcvpueQ0Yn^$&|Ruo-(E*@x`!)_wrAzuM^A3#dLn(I7zZ1Sj64;jz~*M-odO& z$1!u^%lB^hC66^+nm#KdaXbqaw~N2%c=`yydtr#!R<=(e8~bhyAjgyu3(}U4utjXH zDiumAHlF~41#Vn<7uqSSk=$?q>9lfiLbt5m-`DPWXh&{H%j+Ju@i<)MKvv3SRjTb< zzl@L&V0Ne(tWD)uD@)+)Pw3vDkWQ(;y?qY-U9@(B8#}@&wewM%gkbMaMU9=MX*I8 zNV1r#tw}o=IKP5J&7)C^k6?hoEJ}>2Q8|$N7lD?wd*S>98j2Q7C zqKuu^0G=yb01WA%124L=Da5D|S5|05yksatC4!(AbW%~q=md)ME!QTu7X!4nvs_cZ zKXQhhu9V{OCj_unzGPl`keTMNMr+;0BRlZHTL}DX@`T?@4t-W*vxmFyxkja9<5fW`2GU)mvo>HQgY@ zvEhlj7% zmLdpQW%DEbp@sFe8sEgaH6aE&EE5he-b0f{gFoq;hOr+`{oQGDuHegrV#^h-l$KU9 z`*a?ADOa={td7HRGqJ8Tj-*nu6M!TbAOww{nhtP; zTw2$K7yaJEz`@`TOf;z?AWe|@8Jy0jHDgQ2cg7tLkT#Q;Ib}%0Re=jbnow?G>%!#A zk6T3ug4;>1Vq#=A+ET5wB^3YE#EjRx=^K(NW7K{jWj$Le)~r^vcC!7eOzYNZ(loDE zEyu4ik4V3y=hKCPI zQZw$9M2_0aD#p0=fgvJ~7uoIYUkiBL1OHX-YB5?Aoc<8AjUb&+aK6?x@fKUG-tplf z)VJP(@;QgYb^iM0-t6siYRrs9@rPH&+uaufU#=Pc%@~u5KU75Z|YqywQB zuQ?R%P{VsRrGA?~_SwYs_8j(lz*&Sv;mtus#pB;MSnwG7#?Uht<76w&OHXeJ#8hy} z$n{Xry8C9HC-Ay%cMtfahlj(o(j5$u=~zzwL>wXTPsrPYywrZ9vp{_A+gHwvh`&5)0-B8A9I|6T;4tRPnA@etzq--)|~n$DVS_UL_mcr;3?{%r6N$7WqUdL1`2g9$@QO>JOx zsWu&DidFH0hO%k(o2@uyQ~TUNG8H-+;{Hb5=%H!^VZSzkwPk|LFa$NpssDtUBKL`& zlK4CBxcRuNO7aj-q&w*a$&gpf1WZW!X(YamrddFZxSFg?6;dHwgGWO1cKEOrft*a5 ziUeO@7EHBSSdzw1_nb3a?~I-N62qUS`I0iK<*<<=IP}Fblx5_KDU!zcFt_a_zdlFp zMe)QS$m}+W5lc%rD|E~iEuE%Y^dNu#95(n@7$h*6VKMKlpV6diRloQ>jeHh!AxDBv zg~8JH8ETTq%`}-|s12tQRn4{`q3kHVtaHfy*n_L98!&d|q$M4)lg!}!YmC%)F<3K< zTvU|e!>9b|A4@Z8Se+%A?#{hCo&7wqTIA_{5kbx$40(Q`Bb*|L?!^$B$(3Z0%h3^H zlrHeGDs93dA~RcckM)W-6I8@P%qUv z+-3Iz;k#d{)aM#I57SwA_do*O7x;nTr2P`$+ZA$wChp*|cX!1U3;1S}WgHvXEc-J% zw}WQ69Ih&^G93GvGosZMlX$)S*flNp{9g#WmeLyCbWgn!+hZT6s*0X9d(*IQz z%Ip8ssuenI^O-AGv|n{Q`6~L)`x^3dCc9P(QDH!o7%_wIj>~nys6X>reD|2II!0GfEMV{8X=AQc(>-g~2=` zk_(fxG9`l<;=L}WljjQ$8fmngL&){~Rm`0Sg_-W7U_k{?GEHffgR(d2iEXIJ!?!&z*kPDf2uU!F3A6JAP(^QVp5dq(tFMBjbi zJFXrL)UiygfEF(WO9FyJspge#F?%M6Osi>USNSQa@e^}fUV4Mcm?P1h)QTxO=0~|K zFWTZx$nc(XuSs4%V=%Ubhq5Y;wiSjNdDs9i>kxKlf5)sSUzkYO*9sxdxG1bnI+Hl` zVmC@$Rm68~Rys^N%;Yui&Ly~?sva z=heI$5f=e3%IDw{^U($a-zl>kKl4~09{g7eaQwGpzJBl!b>IT59A#_4YAJ8s<}zSW zhmx7iGW8d2QO`Jt0Wa%;K$fV>i0Q_{pxtlU#+}BGyq%zaelFyECC5` z8`m%eSPy6^OPOw#%5mG^NAUP%d_2Gu>iwUl_CLbfaijLHjEao74YRo0^I{RYILCt% zZ1mJ>8RfW>-YH`kr(Yc@Eo9}g{a&GXS@iTA3_?#9w5xItGsN;!02 zf7zWL$CNfw947_{000153@d6g?wfrm+aGS@ zm}um3{7{h~y}U=7hqvpFQN^0ty#Wt<+JwD*nuNWa*0rE|Yb*u?cN-sG_sr&VvX=?g z6wApx0%W$RyBSx~+o+AnRftLQlJDllL;QwC!XCg$4uFmE0eYd5lNWU;mJTj8i}4Vt|ZAiZnUT$Mobn(vMD-0UDuB4>LnpA z;T_0r3>PlOs-18qgP2T{t*~QPs3L7GQiV`La^7}!s_*i)Hgpiu5NBpuz%oFwEz@|T zcC(Q(rm;x>gISPFRg1rB8{hAaD60!TAzweC?i*V(;>Dz%@>oye&R|MCBlQ)?Zo;>L z3~E=%LKLIHsh607()wbYqqfaKHL!BBHQJrEu)LG@^7}-T6y(BIVZPKYvblO z;Oj3_E`!u|;{~+|n@mPv#gPqkd%olRz!&u2>IUhgzqL$@E~{=Fd9G@!yJGEYM%Hyc z_s0Y}uhf2eXTpsgG_B7g9+9BnUA|Es_{u;$933tMKnx*2@7KbD?)}~mb%2CBX(aaT z&b(9(OA=a^MbyBZD*&J@l-V2ROmBzrd~5Lxv9s-y0KC3-aVpgmb$5THH(*7R%3Xfv zq7%W24Wy>cOoEa9i*~CPa#97Sia+bo3X!rg+nx$mounR3C!KvxnZn(ZFxGKd@@5v3 zEhhJXFbf6K7D$dYxt4Y*MJ)>n0fmvL%LAcz|B*)e;emqWy+gQIVNm`J7U;!)B?Alsr z!Y;jw5aW^c6!sI^U}qn`qipz`y)=D;*6f9K?e_o;!{y z0r>Mrab82Kgc$FDW|xy1E~52eohH3>iitISq3D1{!5bap{zSd@qPIEOF{ zP9dmp>bp6NI0f#z8~vazLRty}z1*arC`JUFs6-L|K;?+oZkmr0;kV2FB-XgOY=epd zMY88F%w0V(fwXR7AUu=Oc^!3zcM_*v#0bx~bf?8y68FGfPhetz?Tu;zkiGVVVZFma ze0~XNGZ~#?=6OTU)s2ya%}A=CAZoUDZvaJwMLPA@*6eNmdUq?Zd%d?&t>Jrf#=oj1 zl!gWGLOgr+ul0R;bu{3jOcCIgu-^wmP z>J7NEH9i+SwSZU4!1#7&;quqdLilt_NA=#X`N8~Yx3=Vq#uRX;W@<)h`}?Do|F1`@ zAituK-rq`=3S^?-(<7QxMiiDe$s9+$t0~iZZ6@4t*$7yaj8zyOxuWJ?_q{i3SZbZn z;~u^~2_frfc0`!Zqo9URbgde-qn?V|+T7Ju-BKJ#;JE6NN+Q~x#Kw66^Q5jbLqV=Y z3KXXBi4DnMUkMIA03jj`n(!M2-y7G4ymynkhswm=%*LFwq5&bxfy&6Ud551*$F+os z{xf~L7%x&9Ig&W>7iNMxe=?SI5tY?0=mR=8a|HJ&Gzym|0iQY?U3S*5a`eQ*qV$q8 z53)4sRH?GWOX9p};r0m%Ml5`{$q5qDKCC}Daf?w@aRv_-1Dn>0}#7L5Fu^vB2D#in{nbS|D(4r8&=upvBc@SOAoM@A{criJBq?70d}I!BZoaiDZ-Rf}xhA;N#3tn|3rE z1gFK|GAK|KQB_5FVrM7oMGB4d`4d(+H

2%9jcYqEy~7{B30W4M_qLS{6$VvVjR62RBJJ5aK!2Vy#jSnfEBgC%If6Ft;@H zg}MnCT&tCqvTzq;%T?{eAenttas9So*%je(}jaNADDQfuxbK9_z-2s%mnF+=03=F#^sQ0)j-QgB#?V2d0z z8i}x!K$aLI+7M)AH8IS zofkK%eLg9QUx>=Fchd+<_wGw%yZ;jiC1Cjfyy=FJicSJ!}wXO5mF37N24|x z(U&Ts7|N_*VC>A?dqFBRHaFvDNQ3Lky=d@KOfj=)AszzKoH_Av91;;^15J?B#ruLN zM!x$r3T?7HImJ442d|LAQL|_3?l!NON%6>guOde1dVTb(`~D4bXIJRo#q0#42qfuI zG+M+~<%iQOrkOMBzRW0@uG+u!#{#nmNc7geem4|G>GC5Kec0*(G3JoUKZl54>!2!M zA8Nc|tHVQ;%jw{Dksc10$ua;R=wBak5YNA$lAZOPf9>wF_C2ZkDn=gZFh-@JOt6)# zXTzZ7*lV+~bC-2aYG$`f{unj$HVRvBe#pmK!}Rq@RViiG%l#<>FC|L4Z2#Lc6k~mK z1s#OH>hmux#jW1JXh}Fac&O;Arl4vRY3+KX??%=}b{;e|W=U5&6m`tWHuT8U9O$?b zx^f5fDN@NYzmU~EC?-l$q~jbG&l;pbBQZRUK_BEa#5)ZXGzNJn^PccfvDa`fkS`)H z;x;oxFi<%32lC-ca}aIU<^a4L!aOOfgIcx*b(RdB9{zB2Oy1<|Sn9(uvH`RwQYLpw zHM3j4lt=zh(w1b2Zw738JPr+VP{l8qt!d$wS+0sBh)4X4<)-dtc006o)W<2`- znCr!dxa^EEFrja#;RD!5gku^3FRJd&Aphe78l~l$u;`;fJ>VltB;JIid?nPgUj{L@ z0dwXxT%!1E70bv}1MTbTXRku5;J`QUFir|pnZv7j4Sc5SPyU6PTC3gnq4S;sJ{Mw{Io1T*gkQN zbbIbna4LOuRc;jd=T^-@OB6Y5HpOf7mtk@0Z!?@Y7Ri)Ey{Y1H9%(O%UnfMFa;reu zN#EdTxzA8^jF8Zwh_(0p_I#`5p%6dB_TkV~NAZcbWnImoYn*n?Y@r}#pu{Dh=<&2m zFgoyx!AqCdi)m=uhxsU}F-kIGqz0d+560C(P)Ygh&3m&`>+iH@XTFPuq^mo6&O9R* zEln4pAG}YT31*u}7>8LQ$2>13#@eamepbY46?oEpD_u+gh+JwoU_z8kDRDQ&f!`<` zBV!miW88T9)NKodgXPq!^d?_*Re=;~{QD8?%|^gPJ8tzA%w3c0f0K#KPDVx>reDQ2 z8j_}gzUY!w^~UTboF`E<$^KjI&4y1bIQ^n23eGlaGRh0340CgP7|JSyRuH^U9fIRu z#7(bXT8uV&Ar+s_7t2udZAjR>h|JS-EzV{UI+W2EQ=HuMBcpmLy@gJkImkJhY%Zc~ z*d%h4jmbJNl!=UXK3 z0z*l&^8H`Wm+H5R&$@i7L$5j<=EdB<=F<#(b57J0kZx;_cI%X|9XdLG6mWUzwPjfU z9$|EJ_h2a#?RU$Z*2#pV6D3cXF--Rxlp!IxO0AA)rj?&}{;i!tOIy8p6(HKr?s;&F4^BcdET0mcRW#}i#}ja$@oe|#u4Jxqn8W7MR;@a4 z8rJPSRHz!v*v&*)sSL*z_cnPWy9En|&m8-zq%I=(bWNLw@z$d*f)2vnB`Nczq zkI6((rd5CKx2s7jUan~$q-`>)Bm_>A2l~Oer7M2_X0nMo;Spw_8uc*=zR=&rEK5gA zssO{C;u<>1R&~kMuf&_>HG)GMZXMmkElTR^FhsAEhZ;j$`8n1u9a-g(b!l{qoXP43 zYph)*hujhwUx$`%4f>RqoC4_;NQ2Kz?;<_xjsCeJlZVAFg};5DYYV;iK!bRBT%vk09BwyR?;ok49vj(u50VS%!)uS2)l1+z;Zq>y_A zqYbynYxKE!7}I&~w)FH&+$@M=i=7?F9;1>B^EUMb;`u5nAj6hhg|13}pcg}lw;J^? zNkp2a4|+ofz`Tgm*PyqRI!V-_X5^M?hMMW7Ffee+OAFD?Q%UcEu?r+HdsNmcsdCgW z<)3eOKKBXjK9@(lDK;OYqM)yFL^SmVg{Ho)kyXGngQCtvOWk~-z;uOHnLU4ShILHj zMx+op>2A=;7w)(z*36UcvVvc~FH~{o)N@BJLyf7D9zFNIL<>Fcj}heI9}CX)O~-1;PBa{yo?edJ z8{z`8_ zc3Xv3FyKn;p?p|I_KsdNWq(pJ&;Ts^%slW*>3jRpaC>5o<_>)GEPNf;`13GWGi*lN zRjdk5@+4XyB2vUU=zMWh^+TyU*hDun+|p1*e^BU*EhIy(8Ey zBequ0G+{+|+#E|QlrzdEkyWhxD$^93=H?H&gFio)Cf+PDs7f)OSQK4SWX;+?h-5P0 z^Y^qK`sztbI|kko)8hkW2vB$M1Cv3gPexME0Ri~2@85-l|6IAKcJlqRqI71;Cn%C9QF}1Uck217&4a$fn6AWFwnZQH* z7|H6zotXmbEc*Y|*Vib4e`s$3p2Q;P+)1-DvR{_Gdd&9x`Ru=EHslR+#g)mL3vqJ9 zl_+sby0&N)>plOfd#_zlu*sH_-Q$1!CGZp?8Qpu=wmVe$V|`u!!NT02i!(f#5DUxP zp*HI#Acjs|2>3wI)nyejVpva}>sTcXigf)T52cQ^OADHyeZ*o^CP36QMR^|Ll~~o z|69$;C#LJmwGK2dSVSute1?`8i|!N%EbtH*xc->>>|gtb-_Y{_o8gAieS8C=OQTL> zAWVP->(DS4nQxq3>nM?PKH%Y0gPFOOor8a}K>vpl3BDwJC^{yLfzLf>B^iHcde}gT zIPoeiJQTRtk<;miwP|~E&~9XEVHQ$ofNIF)b5*l@d{_iTf6WkLVq)^JV!0 zwXNJ9$>QSRVl-R&!~&tgelfEDfHr@8a`yj1q7vtq?);)i|JmM`r87;rnL%5iWJ1Uh zfo{_^J3o)Um{rfgy)|}m;b`Cum~8}nc`*{}(HkQ9osugB!3*{%ir8F^V`b&bHR9t@ zTiE-CL4g_%PmgZ*$IOQ7tf(Zy{jD{;x;h5O{%i&1TCEe0KZiIoQ$HqQy`AQ`__#wH z1@rw*sJXa!@r(Sz4n~D~$!;!;gMR2RkIs*iw_Umc7P#M^lDNF>3nO2pM@MBjX7H0l zU)h%xGMs~eUqqf4i>QrtsM2PVFDKb~LqY zsuX>o_I6^~&SYs;Y zXRuurRPgBPl3z$eulIaejrR88M1uAr70M^+3L-7+YNkAT`4Gq14dw&qF;OvK{x#nR zhr`$Vhu`t?vBTQix5({NL=>ynHS={Vea@3h>ahuLcea(@&OTXu-IOOB=Se~?$uNd$ zBPrc(tEGLl{<>*693r(An~l&}OAb%IN;B>ZukbzeJKQG35JdY$qfwzW89r@Rta&bN z1ukNMQ!SuMkaUu}F`m0u+wy62Y7Ni$;G;b0k^yBwA$itAqYh{M@n5#lc*;dV*oLn9 zDSoci{I4vv$)P2h&}+#(Npcs%$n(%kZh3GgPdzBcNgV0U3c2BtLUS(}F#6q*8sZBf z$CQPwEg1U30ha6W5#GwzNn??zqK|==kEOZbTtFWP^K*plQwWdM;2*g2+#lB~s0_Er z5$Pn=&wtUKL6@-BA~*0^tV-_ZZ$hh9^=V3APxMY&vHB;Dmm0QTvv- z=;MqLD-CO_izBHb+V*8G=+=F!i$5BJOUFoWG{*LyHWC<2WaYeNYVxZn<*%v8+XUKz z=$d4e=JBLHSHN8h*>T2ofu)5-GFrfmYg{CyI@k*bDvR7}keOf2LakqM!=aLx`1)oX zmE*v|zWMA6Hrx)N2@gDE*grfScLV`>nC$H}C%@9tj&}H7qH4U;qC@d0(2UY1x;);o zAkUbXn};uFeEsMjSvmyc{}9FOQg?Pp!Bwp}(6YDB8t;>B`#GE2%}r46 zH%E7uk_k4lC^zsoL(1oIL zNdwz&n~yEkV9ZU=lgl~R*8`kH64A-YJI;{=#H5~2?o)^Skw_ZWKS7anqIdX3FR|7y zPws4Y14#iPZk-QxxrcIs9RbmyP^!aPmWKNWF4Y@4<2U!hk3FpE z?VQ6wBBGxy^2dNo-bb`GJ?plSh=N zO1Qg^ly_|Z=MwwOr3Du_^%qc^{D8j@(u*+p^SWb;8A`!mX@|T1TV~=}ZVUxkveLOT zM9r4nd%5C$iP}&?vJ=*J2BvJ(#yiB+L1kdBsNr7pa}=}JZ4jMHg05aSd$OF#%C#)F zlGidtF55x#44aXa{MA3Wp@=P@1mMV2cdkvx|9*+Q?hzGnn#L2jf`oG+lOWThQnym9 zhsW&@dWm?}rf5qw#|Dy28`~*5BWLymW$gm>UnZrpFc~Bcnz>j#ek>tt`uK>8ZPnhZ zeu6}0)2=o*7~SO8*tx55Mg|XvC%rqk+@lw-{nI0BvV1?Wk}$ZGYo)vdSr(`tQWsjQ7DmOVQHUtj%ZO{=@{woWh&ejS|DykI zTomQ$Z>`3_$8TSJi2S16@`XIp?doPH7YLEOGaJumahsAdU&d~vM2F0w|2z#wx)8bl zkg}(5rihSW_>0?Y6YKn21P|)lKAJuyUIDbmixGK*T`-!I#6yy?r1$RH13pIH$UMkb zf5gk(nM+CXTwaIYSI@2V=s}Uq z9IR=Pf?n{0uyTopxj`sr<8u^+?uBKo5-T1`>h zR5HaFt%LiJOAxOyU%S>BCY>?vY|B19GZ+gQMKb)#Bh1<__2)YshdILW_0~ZxR`EHk z#G-4yK&bX%tD>>V&TDKRK@NEv$T=sh180;?X;^ZQvj9l}D_)Q#M`+0dz|puc-cKeRom>rm5|$kpwZR>~iaCFsQ5mTuzOBjwt&l%mx{ z)nxX*Yv^?HF2W`pj2mLVwUJ`gq)78yqq%IyyMeE1%KQI!Y&Mi}p_(*ae%l0Tleo9= zQJCsb#p9iDuQSQ3GS0}GLsff9O&*;rS|=|^WI@futGOWk@!Ut`b)Io|HxKCpzRn$X zHhBM(H>GqTArekjI`X4`p;-xR#K`6de_fn@l9h&)9gI` zlH1Twc({Bl>X2eYW#nJB?cO_KsV!v3=7&nvjcV~AcTQA*7z-tNGHpFtLf64~3Ksk` zk3EufM9RU+C4Xj?wvTc3@T^a;uEaq@-uxY#GTkd)Y7hqxi@cyQRazyyze5=mCNB2@ z6ca7DNL9kXtxQhSp|0MX<}s2s%uF)FUBxSeNrZE@R3+egQ02s}97YWiZzbYd<2I%wY>QXkf<hI^5=$~CM(#_M2@8X~& z#yLP4jl?9Q6BqNXLkw42%?Xmhfc%JUya+u!y@pmWRVGB)ubd9J@(SjV&F0q_BD^X>J6v~M6y;6Fc1l5W2xjY8%@q4|0fqDbbxW$uo@LCorr zjepG)op3ueK0z~a-}5;_6e{4qn{MdIIzqJ`t+DMoi!Jq7jk*o)IH^VPV0~#6|f*60Bc?U~3jrOKO~w_lg+f3>?}$%vXtu zi5aiO8=`d62WlNMK~y@2Pdq}J2k3A`J7YO%ks(3u;i8G0=m>&|)mfZT;Y=RKNekQl z37sp9Luy6GTc#_jPOdM7?eq$}lXhjR*~Ut=yeM^T^pQAe{ z57+i-SiQUBOCx$}Hdh^qC+;c0aWH5kh!|^(V`T$)!xj;0se2V2BGOSKyw5LtkZ+7U zGAtz@&%rUO;}vI#eC`1^=N@31@N|X{MWk)ZN<6SZ8dQ3xgSpNqbu(}X;$;2~kntfq z$iHXlA5Gc%scGWO-k-@)-#Go1#mgh5$82~xfdV6Qh6%8?VSj@MlZ%=ue?-icttQL@rer&cc{3-6EeIpnUJHP7t;QM3! zF=-{oT*ZzJCfKiAqy}!6hmcES%B}LE$$&zAL1*%y=LDpSOQ$Abd^g+nv2lKS#nNBC zdw+38VwRV`FL2YuQI^sloGsVx}EPGE{ z+)tI&HzPd?p?JgG8GPrfQj4zvDyzCwPfd1_Z+Esy~rw zW~G*J$7yQ`jJhzb5itcj7(@|*-@R+nrGLdtH{s^D^b3`{V;>1+;ub_&?!k+^Anor$ z^`5c){!obyehlSgbWhzqB$$fWb)>KPjyULdpSJ8L@=cLaGKX){?(+GD8ISSlY=7Hg z9x&94{veI(fX$jmFi`xN&;3>8Lvifys|tW(y}(=)TQjO11(NRatB)9yO}y=Zk!|EL6@cJAB0c&wj01ffeeL3F9qr>3OG&nO$7=bPYOX7NomRR@ zve1!hbt{~${rUQEs}mA1ckSwOdg#*waJ5c5JR~auNRkQgF$?>0_RMbn^qX>q2Wb&# z10yqc!>d85XV&rv(H|g6BzY)XL|f?GD(730qi^ZUF3JYq>j10p@swjW+^FjpQa9H1 zRZmEv#_;c@ zodG3=0bZSw%I(lZw9x(N2N5@Hnl+J=t=PM$7feur==g*Exb-vt*>=j>{h-b3NXjUuB35Lv4th`(@@75UYM3)g^S$ElocuUo z3=dNAKOHjY{a`Kw#W2GWNpr0zk9Xee?o!Sn*0y3}XTPCi21uJ&u#94Z)%bTd>-3SFU3)OGOZJ0BoBFYoZ*L>eFX^w==7xBmfV*uaKEc6WEbU(SdEPs3MHAQ~)d z#yk^mi<6e^kXZWQzJm=$;7bmMuk&;}ihVS5-e#iFE<5pSoS60-03*EN0;$B1STbMS z2!~A-?RKrZV}_HlKqO9%teH@cD6l(-e59qwvQO=V1wqy2Q?9F z!{eJCCIg>W=Bz^B=$ao>$eYppfx8TuNx|7yf_eX(vV_jnGWj3HnP&O#{>Yv(`|ih- z!E3i}5JF4uk54Y3ff_-7jz{2$RAFO*ARc#jcRm20-vd2(4EW59G6eYgHavL&uT#dy zUP&UVe`WAZZR!X`wgGOa z_JL(!d6!CJ#E6*xU@h4qk(uM)>pv4Tjhekpw0Gv#`8ha$SIx+nl&YuYu>)81?BNPr z2u@T;Ng|giZ3wRoD^cWpdjiaIbjoWa;8|=8*n*OzBvH)-!FS)hIYUfylm+Y)NI1 za|m4-yIvS#ZfY9IJjLgJjI4(3da=eKP=E?g`Ocfc6_6Z>O= zYB%Bu3mxv zj{L~idVmfCTDjK51l7K1lF20qGl9B*IpkIh-z zDFplPIMfh|L&W|!9ulaP8YrM&*ib3;;NPn!`ok|qpO16=_oAwQPk&x`HgFOjT|CMn zM^Tkhmk{Id;Yl!MJAGAIgzYhM4t~2nKBk@J2P-%DWbwc1jDElBPI`UyH43v0XOEEJqFuy~naucRj#O|-c%u<8KP)?2$u%PehLR+6OvqHjZW zJ+%@J&yBA&$B1Ud`N+!7y58%ORMZ~U*4Flrj9;LN-j*u)ye{yy>kTO6F|WGLSyQP^ z0|N`dG$vN#-sxB@I-iHXvY|zW03((|`8mgcoA$wuvj&lU-rw&$_+U%Ar}`sjf~>IB z<>Hs6{TK%^fdW9XhwpCRAXh9`JoD|wSrnoc<>OcTYZN(KV&Hcpcu?wE1MiyLXja+= zV7RFPQwh^%7+ii?h}z{PTGfJX%dLLH>clx&{q~=?VzFCm*RQMp3uLz+ZDg^G8&>D{ zf)`eAAKtG~33#~*t~Zg8E{3!Yaf{$WwHKAwJ?6s;xLDK@RU8I2bSf=cc0PZd-a9y+jpeo|MqdLe! zI*PBTs|gs>sf}eHeS7>+(hOY8!3xP3>^O9(;Zx6Pt zMt{c36|;1Vj7Is#z;Dww_dgD0W>(hg&U0^cYbKapVm@7KGOIPVZf5X^2WD*P1yj7_ zef?)H@S?F?v?X+zxnJ)&{wlix865mK_Qq-$%^Ny5#lH<@|4HfiR|(Ix+8Z`xc!XsWmQkD# z{uZXsiXqWpU)Lt^NF+_@m1j7|D*^w$M?ba{K$OgoHdItA#Jv#U<@OLcT2iG*WF~`2 zJEkJerOj$0^hquH5^k(n`!vweSd)EvTZk#O|jpZPDv+5h? z0RH;H{@6zdDq1?2XuJM3xRAOEXQafXGbXnwwf{3xZpEj?O!bZwFvyTv@C{bcfvdZd z=NF1j%)}4kCaK6B=k;Gp=>fyBs6B#;0w zTbi1NGEaf67Jb}_S9ieMVv&`rYl_fZjY;FL-XcF8!R;%3G|<`khqCsEZCAq&Yi(tshy;hXrtPHnOC3c$FVcl0)# z>_ho6Fk1eP$>_ogFh@P}(J@Y315?ug0w6)_r>JVEK>{iE16n)LLw8Iy!kWScbZ&w7*t` zum2LFhVw6>@*@{~)+UAb^5q&Z&9BhHy(8>cee#q^=%k>{-bbl~{!h{q*k9Ox?Z6UC z^f!Wb#fLtqwWD!G+0l+Or62I4(N&w1mxsx1DO8yaBTb{407t-`;{HM>9eU zP1-k}s_RH$tkNw9Uhe_Ed!caPLBYMFSgJw;bHN~YQlCA@TxC&YOR)O|CXD~5)5idS zd@2lJBYyQke=s_D|E)Hz@6BTO&6VjWSwE?@@sCZmJ;9S-B50n2@D-wwd29}|NJnMZp`)pmZ4~mk^2N6R0zY}j~d($aE4qKvM%~KfpJsStKebN;K z*Kp~510eM%()1s?fN0#=@$UiF^6csk6q$NKoY(XJzfnj|?Nd)rJhr)a%MBm`7eq49a^;%} z+B4s;1J$bNt+xd`L}xX^xIs8DQOFr$G@=gi*~)>^&7W(QzjE<^;&s=JxW7^h;i3h9 z!pHZKhx4+1qNM`Ni_HQO6GKEP^!rKg)iR8?z=lsFLJW<@UporGATNhdz+Z5!6JAL6 z8-wmB`(C7zn9ozFQAqtxFzy1zRfFFQyS7o{D91Li?TNSvr?~Twu%!pGdLQIT18xqL zqJorZSD$O5I}t%rQvaTQLj2E4{1aAd@W^WwrGM%ETa312NZ#?8TD1N>tA<+mVS8Va} zU7_B6dkgyC&CM47zneq5l3BUSL%o?K_{=2g5HbYfJw2ZFghTb8KJDarIw{af_BS>- zn@+kJ2^v#m*P>bG;5^MB0-)TBd1_>_-WHqd0ER(@1VR1f^2G4=mDGVekUf~JBFjzrSLi(Vl}?S+wRRW%Ep zJnP#zYc^EDcKHA&^#8UAqw{elxAf0vTa^7OFZhBfz)uDoh`9f$B%DSMiVj$&Q8CZ4 z|NBGCI9ytoDENPfN;Z7{ySM*V%4YWe`=`_. -CoRelAy is available on PyPI and can be installed with: +At the core of CoRelAy are **pipelines**, which consist of a series of **tasks**. Each task is a modular unit that can be populated with operations to perform specific data processing tasks. These operations, known as **processors**, can be customized by assigning new processor instances to the tasks. -.. code-block:: console +Tasks in CoRelAy are highly flexible and can be tailored to meet the needs of your analysis pipeline. By leveraging a wide range of built-in configurable processors with their respective **parameters**, you can easily adapt and optimize your data processing workflow. - $ pip install corelay +.. note:: + + If you come from a previous version of CoRelAy before the 0.3.0 release, please refer to the :doc:`migration guide ` for information on how to transition to the latest version. Some breaking changes have been introduced, and the migration guide will help you adapt your existing code to the new version. Contents --------- +======== + +This documentation is organized into four main sections: + +* **Getting Started** -- In-depth information about all features of CoRelAy, including installation instructions and usage examples. +* **Migration Guide** -- A guide for users of previous versions of CoRelAy to help them transition to the latest version. +* **Contributors Guide** -- Guidelines for contributing to the project, from reporting bugs and creating feature requests to contributing to the code base and documentation. +* **API Reference** -- Detailed descriptions of the modules, classes, methods, and functions included in CoRelAy. .. toctree:: :maxdepth: 2 - getting-started - reference/index + getting-started/index + migration-guide/index + contributors-guide/index + api-reference/index bibliography -Indices and tables ------------------- +Indices +======= * :ref:`genindex` * :ref:`modindex` -* :ref:`search` - Citing ------- +====== -If you find CoRelAy useful, why not cite our paper :cite:p:`anders2021software`: +We encourage you to cite our related paper :cite:p:`anders2021software` if CoRelAy has been useful for your research. To make it easier, we've included the relevant citation information below. .. code-block:: bibtex @article{anders2021software, - author = {Anders, Christopher J. and - Neumann, David and - Samek, Wojciech and - Müller, Klaus-Robert and - Lapuschkin, Sebastian}, - title = {Software for Dataset-wide XAI: From Local Explanations to Global Insights with {Zennit}, {CoRelAy}, and {ViRelAy}}, - journal = {CoRR}, - volume = {abs/2106.13200}, - year = {2021}, + author = {Anders, Christopher J. and + Neumann, David and + Samek, Wojciech and + Müller, Klaus-Robert and + Lapuschkin, Sebastian}, + title = {Software for Dataset-wide XAI: From Local Explanations to Global Insights with {Zennit}, {CoRelAy}, and {ViRelAy}}, + year = {2021}, + volume = {abs/2106.13200}, + journal = {CoRR} } - diff --git a/docs/source/migration-guide/index.rst b/docs/source/migration-guide/index.rst new file mode 100644 index 0000000..82b0633 --- /dev/null +++ b/docs/source/migration-guide/index.rst @@ -0,0 +1,10 @@ +=============== +Migration Guide +=============== + +This migration guide provides information on how to transition from older versions of CoRelAy to the latest version. It is intended for users who have previously used CoRelAy and are now upgrading to a newer version. The guide outlines the changes made in the new version and provides instructions on how to adapt your code accordingly. + +.. toctree:: + :maxdepth: 2 + + migrating-from-v0.2-to-v0.3 diff --git a/docs/source/migration-guide/migrating-from-v0.2-to-v0.3.rst b/docs/source/migration-guide/migrating-from-v0.2-to-v0.3.rst new file mode 100644 index 0000000..d1d6da6 --- /dev/null +++ b/docs/source/migration-guide/migrating-from-v0.2-to-v0.3.rst @@ -0,0 +1,78 @@ +=============================== +Migrating from v0.2.* to v0.3.* +=============================== + +Between v0.2.* and v0.3.*, CoRelAy has made some significant changes, including, but not limited to, the following: + +* The project was converted from a ``setup.py`` project to a ``uv`` project. +* The Python dependencies were updated to the latest versions. +* The linting of the project was improved. +* The docstrings of the code were improved. +* Type hints were added to the code. +* The code was refactored to improve readability and maintainability. +* The test coverage was improved. +* The documentation was improved. + +For more information on the changes made in this version, please refer to the :repo:`CHANGELOG.md` file in the root directory of the project. + +The most important change, however, is how slots (:py:class:`~corelay.plugboard.Slot`) are defined. Slots are the underlying mechanism for parameters (:py:class:`~corelay.base.Param`) of processors (:py:class:`~corelay.processor.base.Processor`) and tasks (:py:class:`~corelay.pipeline.base.Task`) of pipelines (:py:class:`~corelay.pipeline.base.Pipeline`). In the new version, instead of assigning an instance of a slot (:py:class:`~corelay.base.Param` or :py:class:`~corelay.pipeline.base.Task`) to a class attribute (of a :py:class:`~corelay.processor.base.Processor` or :py:class:`~corelay.pipeline.base.Pipeline`), the slot is now defined by declaring a class attribute of type :py:class:`~typing.Annotated` with the data type of the slot as the first argument and the instance of the slot as the second argument. + +This change was made necessary because the project now uses type hints and MyPy to statically type check the code. In Python, class attributes can be accessed using the class or the instance. For example, if a class ``Test`` has a class attribute `a = 5`, then `Test.a` and `Test().a` will both return `5`. Static type checkers, like MyPy, do not know that we are dynamically converting class attributes that are slots to an instance attribute of the specified data type at runtime, so they will assume that when a slot is accessed using the instance, it will have the same type as the class attribute. This means that the static type checker will assume, that a slot, defined as `parameter: Param(int, 5)`, will be of type :py:class:`~corelay.base.Param` and not :py:class:`int`, and will produce a type error when the slot is accessed using the instance. + +:py:class:`~typing.Annotated` is a special type that allows us to annotate variables, parameters, and attributes with metadata. It signals to static type checkers that the variable, parameter, or attribute is of the type specified in the first argument. So a slot, defined as `parameter: Annotated[int, Param(int, 5)]`, will present to MyPy as an :py:class:`int` and will not produce a type error when accessed using the instance. The second argument of :py:class:`~typing.Annotated` is the instance of the slot, which is used to define the slot at runtime. This change allows us to use type hints and MyPy to statically type check the code, while still being able to define slots dynamically at runtime. + +To update your code, you will need to change all slot definitions, i.e., all uses of :py:class:`~corelay.base.Param` and :py:class:`~corelay.pipeline.base.Task`, to use the new syntax. For example, if you have a processor that looks like this: + +.. code-block:: python + + from typing import Annotated, Any + + from corelay.base import Param + from corelay.processor.base import Processor + + class MyProcessor(Processor): + """An example processor.""" + + parameter = Param(int, 5) + """A parameter that was defined using the old syntax.""" + + def function(self, data: Any) -> Any: + """Applies the processor to the input data. + + Args: + data (Any): The input data that is to be processed. + + Returns: + Any: Returns the processed data. + """ + + return data + self.parameter + +You will need to change it to look like this: + +.. code-block:: python + + from typing import Annotated, Any + + from corelay.base import Param + from corelay.processor.base import Processor + + class MyProcessor(Processor): + """An example processor.""" + + parameter: Annotated[int, Param(int, 5)] + """A parameter that was defined using the new syntax.""" + + def function(self, data: Any) -> Any: + """Applies the processor to the input data. + + Args: + data (Any): The input data that is to be processed. + + Returns: + Any: Returns the processed data. + """ + + return data + self.parameter + +For the time being, the old syntax is still supported for backward compatibility. It is, however, highly recommended to update your code to use the new syntax, as the old syntax will be removed in a future version of CoRelAy. Also, this will cause an error when you are using a static type checker like MyPy. diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst deleted file mode 100644 index abd6c38..0000000 --- a/docs/source/reference/index.rst +++ /dev/null @@ -1,17 +0,0 @@ -================ - API Reference -================ - -.. autosummary:: - :toctree: - :nosignatures: - :recursive: - - corelay.base - corelay.io - corelay.pipeline - corelay.plugboard - corelay.processor - corelay.tracker - corelay.utils - diff --git a/example/corelay_basics.py b/example/corelay_basics.py deleted file mode 100644 index ad39e09..0000000 --- a/example/corelay_basics.py +++ /dev/null @@ -1,118 +0,0 @@ -"""An example script, which demonstrates the usage of CoRelAy's pipeline and processor classes.""" - -from types import FunctionType -from typing import Annotated, Any - -import numpy - -from corelay.base import Param -from corelay.pipeline.base import Pipeline, Task -from corelay.processor.affinity import Affinity, RadialBasisFunction -from corelay.processor.base import FunctionProcessor, Processor -from corelay.processor.distance import Distance, SciPyPDist - - -class MyProcessor(Processor): - """A custom CoRelAy processor, which applies a configurable function to its input data and multiplies it by a configurable value.""" - - multiplier: Annotated[int, Param(dtype=int, default=2)] - """An integer parameter, which is multiplied with the result of the function. - - Note: - Parameters are registered by defining class attributes of type ``Param``. These will be automatically realized as instance attributes at - runtime and can be initialized in ``__init__`` using positional and keyword arguments of the same name, depending on the ``is_positional`` - parameter of the ``Param``. The first value is a type specification, the second a default value. - """ - - function_to_apply: Annotated[FunctionType, Param(FunctionType, lambda x: x**2)] - """A function, which is applied to the input data. - - Note: - As class methods have to be bound explicitly, ``function_to_apply`` here acts like a static function of ``MyProcessor``. If you want to have - processor that applies a custom function and that is bound to the class and has access to self, please refer to - :obj:`corelay.processor.base.FunctionProcessor`. - """ - - def function(self, data: Any) -> Any: - """Applies the custom function ``function_to_apply`` to the input data and multiplies it by the parameter ``multiplier``. - - Args: - data (Any): The input data that is to be processed. - - Returns: - Any: Returns the processed data. - """ - - # Parameters can be accessed as self. - return self.multiplier * self.function_to_apply(data) + 3 - - -class MyPipeline(Pipeline): - """A custom CoRelAy pipeline, which applies a series of processors to its input data.""" - - pre_pre_process = Task(proc_type=FunctionProcessor, default=lambda self, x: x * 2, bind_method=True) - """A pre-pre-processing task, which applies a function to the input data. By default, the input data is multiplied by 2. The ``FunctionProcessor`` - class is a ``Processor`` that applies a function to the input data. - - Note: - Tasks are registered by creating a class attribute of type ``Task`` and, like parameters, are expected to be supplied with the same name in - ``__init__`` as a keyword argument. The first value is an optional type that determines which type of ``Process`` is expected, second is a - default value, which has to be an instance of that type. If the default argument is not a ``Process``, it will be converted to a - ``FunctionProcessor``. By default, functions fed to ``FunctionProcessors`` are by not bound to the class. To bind them, we can supply - `bind_method=True` to the ``FunctionProcessor``. Supplying it to the task changes the default value of the ``Processor`` before creation. - """ - - pre_process = Task(proc_type=FunctionProcessor, default=lambda x: x**2) - """A pre-processing task, which applies a function to the input data. By default, the input data is squared. - - Note: - The ``bind_method`` parameter is omitted here and therefore defaults to False. This means that the function is not bound to the class and does - not have access to ``self``. - """ - - pairwise_distance = Task(Distance, SciPyPDist(metric='sqeuclidean')) - """A task, which applies a pairwise distance function to the input data. By default, the squared euclidean distance is used. The ``Distance`` - class is a base class for all distance processors. - """ - - affinity = Task(Affinity, RadialBasisFunction(sigma=1.0)) - """A task, which applies an affinity function to the input data. By default, the radial basis function is used. The ``Affinity`` class is a base - class for all affinity processors. - """ - - post_process = Task() - """A post-processing task, which does nothing by default and returns the input data as is.""" - - -def main() -> None: - """The entrypoint to the corelay_basics script.""" - - # Creates a new pipeline without specifying any parameters, which means that the default values of the tasks will be used - pipeline = MyPipeline() - first_output = pipeline(numpy.random.rand(5, 3)) - print('Pipeline output:', first_output) - - # Tasks are filled with processors during initialization of the Pipeline class; keyword arguments do not have to be in order, and if not supplied, - # the default value will be used - custom_pipeline = MyPipeline( - - # By setting the ``bind_method`` parameter to ``False``, the function is not bound to the class and we do not need to a ``self`` argument - pre_pre_process=FunctionProcessor(processing_function=lambda x: x + 1, bind_method=False), - - # The ``pre_process`` task is set to a custom function, which is not of type ``Distance`` and is therefore automatically converted to a - # ``FunctionProcessor`` - pre_process=lambda x: x.mean(1), - - # The ``pairwise_distance`` task is omitted and therefore defaults to the squared euclidean distance; the ``affinity`` task is set to a - # ``RadialBasisFunction`` with a lower sigma value - affinity=RadialBasisFunction(sigma=0.1), - - # The empty ``post_process`` task is set to an instance of our custom processor ``MyProcessor`` and the ``multiplier`` parameter is set to 3 - post_process=MyProcessor(multiplier=3) - ) - second_output = custom_pipeline(numpy.ones((5, 3, 5))) - print('Custom pipeline output:', second_output) - - -if __name__ == '__main__': - main() diff --git a/source/corelay/__init__.py b/source/corelay/__init__.py index d910efd..1841ec2 100644 --- a/source/corelay/__init__.py +++ b/source/corelay/__init__.py @@ -1,4 +1,5 @@ -"""CoRelAy is a package to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps (``Task``) with -default operations (``Processor``). Any step of the pipeline may then be individually changed by assigning a new operator (``Processor``). Processors -have parameters (``Param``) which define their operation. +"""CoRelAy is a package to compose small-scale (single-machine) analysis pipelines. Pipelines are designed with a number of steps +(:py:class:`~corelay.pipeline.base.Task`) with default operations (:py:class:`~corelay.processor.base.Processor`). Any step of the pipeline may then +be individually changed by assigning a new operator (:py:class:`~corelay.processor.base.Processor`). Processors have parameters +(:py:class:`~corelay.base.Param`) which define their operation. """ diff --git a/source/corelay/base.py b/source/corelay/base.py index 5e8c2a2..563b420 100644 --- a/source/corelay/base.py +++ b/source/corelay/base.py @@ -1,32 +1,41 @@ -"""A module that contains basic CoRelAy classes, such as ``Param``.""" +"""A module that contains core CoRelAy classes, such as :py:class:`~corelay.base.Param`.""" -from typing import Any +import typing from corelay.plugboard import Slot +from corelay.utils import get_fully_qualified_name class Param(Slot): - """A single parameter, whose instances are tracked by a ``MetaTracker``.""" + """A single parameter slot, which can be used to parameterize implementations of :py:class:`~corelay.processor.base.Processor`. The instances of + :py:class:`Param` in a :py:class:`~corelay.processor.base.Processor` implementation are tracked by a :py:class:`~corelay.tracker.Tracker`. + """ def __init__( self, - dtype: type | tuple[type, ...], - default: Any = None, + dtype: type[typing.Any] | tuple[type[typing.Any], ...], + default: typing.Any = None, mandatory: bool = False, positional: bool = False, identifier: bool = False ) -> None: - """Initializes a new ``Param`` instance, and configures its type and default value of the parameter. + """Initializes a new :py:class:`Param` instance, and configures its type and default value of the parameter. Args: - dtype (type | tuple[type, ...]): The allowed type(s) of the parameter. This can be a single type or a tuple of types. - default (Any): The default value of the parameter. This must be an instance of one of the types specified in ``dtype``. - mandatory (bool): A value indicating whether this parameter is mandatory. If `True`, the default value will be removed. - positional (bool): A value indicating whether this parameter will have to be passed as a positional argument to ``Processor.__init__``. If - `True`, the parameter will be passed as a positional argument to ``Processor.__init__``, otherwise it will be passed as a keyword - argument. - identifier (bool): A value indicating whether this parameter should be used to identify a ``Processor``. If `True`, the parameter will be - used to identify a ``Processor``. This is useful for distinguishing processors, when caching their outputs. + dtype (type[typing.Any] | tuple[type[typing.Any], ...]): The allowed type(s) of the parameter. This can be a single :py:class:`type` or a + :py:class:`tuple` of multiple :py:class:`type` instances. + default (typing.Any): The default value of the parameter. This must be an instance of one of the types specified in the ``dtype`` + parameter. + mandatory (bool): A value indicating whether this parameter is mandatory. If :py:obj:`True`, the default value will be removed. Defaults + to :py:obj:`False`. + positional (bool): A value indicating whether this parameter will have to be passed as a positional argument to + :py:meth:`Processor.__init__ `. If :py:obj:`True`, the parameter will be passed as a + positional argument to :py:meth:`Processor.__init__ `, otherwise it will be passed as a + keyword argument. Defaults to :py:obj:`False`. + identifier (bool): A value indicating whether this parameter should be used to identify a + :py:class:`~corelay.processor.base.Processor`. If :py:obj:`True`, the parameter will be used to identify a + :py:class:`~corelay.processor.base.Processor`. This is useful for distinguishing processors, when caching their outputs. Defaults to + :py:obj:`False`. """ super().__init__(dtype, default) @@ -34,25 +43,42 @@ def __init__( if mandatory: del self.default + self._mandatory = mandatory self._positional = positional self._identifier = identifier + def __repr__(self) -> str: + """Returns a :py:class:`str` representation of the :py:class:`Param` instance. + + Returns: + str: Returns a :py:class:`str` representation of the :py:class:`Param` instance. + """ + + # Sphinx AutoDoc uses __repr__ when it encounters the metadata of typing.Annotated; this is a reasonable thing to do, but then it tries to + # resolve the resulting string as types for cross-referencing, which is not possible with the default implementation of __repr__; to be able + # to get proper documentation, the fully-qualified name of the class is returned, because this enable Sphinx AutoDoc to reference the class in + # the documentation + return f'~{get_fully_qualified_name(self)}' + @property def is_positional(self) -> bool: - """Gets or sets a value indicating whether this parameter can be assigned as a positional argument to ``Processor.__init__``. + """Gets or sets a value indicating whether this parameter can be assigned as a positional argument to + :py:meth:`Processor.__init__ `. Returns: - bool: Returns `True` if this parameter can be assigned as a positional argument to ``Processor.__init__`` and `False` otherwise. + bool: Returns :py:obj:`True` if this parameter can be assigned as a positional argument to + :py:meth:`Processor.__init__ ` and :py:obj:`False` otherwise. """ return self._positional @property def is_identifier(self) -> bool: - """Gets or sets a value indicating whether this parameter should be used to identify a ``Processor``. + """Gets or sets a value indicating whether this parameter should be used to identify a :py:class:`~corelay.processor.base.Processor`. Returns: - bool: Returns `True` if this parameter should be used to identify a ``Processor`` and `False` otherwise. + bool: Returns :py:obj:`True` if this parameter should be used to identify a :py:class:`~corelay.processor.base.Processor` + and :py:obj:`False` otherwise. """ return self._identifier diff --git a/source/corelay/io/__init__.py b/source/corelay/io/__init__.py index 2920183..bc8732d 100644 --- a/source/corelay/io/__init__.py +++ b/source/corelay/io/__init__.py @@ -1,4 +1,7 @@ -"""A sub-package containing IO-related modules for ``Processor`` data.""" +"""A sub-package containing IO-related modules for storing intermediate results of operations performed by instances of +:py:class:`~corelay.processor.base.Processor`. This can be used in a :py:class:`~corelay.pipeline.base.Pipeline` to prevent the re-computation of +intermediate results needed multiple times, or as a cache for subsequent runs of the same pipeline. +""" from corelay.io.storage import ( NoDataSource, diff --git a/source/corelay/io/hashing.py b/source/corelay/io/hashing.py index da8beda..83d5dc9 100644 --- a/source/corelay/io/hashing.py +++ b/source/corelay/io/hashing.py @@ -1,54 +1,57 @@ -"""A module that contains non-cryptographic hashing functionality for Python objects. +"""A module that contains non-cryptographic hashing functionality for Python objects. These are used to compute hashes of the inputs of operations +performed by instances of :py:class:`~corelay.processor.base.Processor` to identify them in a way that is independent of their memory address +and can be used to identify data between subsequent runs of the same :py:class:`~corelay.pipeline.base.Pipeline`. Note: - See https://github.com/chr5tphr/funcache/blob/master/funcache/hashing.py to see the original implementation of this module. + Please refer to the `Funcache Project `_ to see the original implementation + of this module. """ import importlib import pickle +import typing from types import ModuleType -from typing import Any, Protocol, runtime_checkable +from typing import Protocol, runtime_checkable import numpy from metrohash import MetroHash128 # pylint: disable = no-name-in-module from numpy import ndarray -from numpy.typing import NDArray @runtime_checkable class SupportsConversionToNumPyArray(Protocol): - """A protocol that defines an interface for objects that can be converted to a NumPy array.""" + """A protocol that defines an interface for objects that can be converted to a :py:class:`~numpy.ndarray`.""" - def numpy(self) -> NDArray[Any]: - """Converts the object to a NumPy array. + def numpy(self) -> numpy.ndarray[typing.Any, typing.Any]: + """Converts the object to a :py:class:`~numpy.ndarray`. Returns: - NDArray[Any]: Returns a NumPy array representation of the object. + numpy.ndarray[typing.Any, typing.Any]: Returns a :py:class:`~numpy.ndarray` representation of the object. """ class TensorPlaceholder: - """A placeholder class to stand in for PyTorch's ``Tensor`` class in case PyTorch is not installed.""" + """A placeholder class to stand in for PyTorch's :py:class:`~torch.Tensor` class in case PyTorch is not installed.""" - def numpy(self) -> NDArray[Any]: - """Converts the PyTorch tensor to a NumPy array. + def numpy(self) -> numpy.ndarray[typing.Any, typing.Any]: + """Converts the :py:class:`~torch.Tensor` to a :py:class:`~numpy.ndarray`. Raises: NotImplementedError: This method should not be called, as this is a placeholder class. Returns: - NDArray[Any]: Returns a NumPy array representation of the PyTorch tensor. + numpy.ndarray[typing.Any, typing.Any]: Returns a :py:class:`~numpy.ndarray` representation of the :py:class:`~torch.Tensor`. """ raise NotImplementedError('This method should not be called, as this is a placeholder class.') Tensor: type[SupportsConversionToNumPyArray] -"""Either the PyTorch ``Tensor`` class or a placeholder class if PyTorch is not installed. +"""Either the PyTorch :py:class:`~torch.Tensor` class or the placeholder :py:class:`TensorPlaceholder` class if PyTorch is not installed. Note: - This is used to check if an object that is to be pickled is a PyTorch tensor or not, because PyTorch ``Tensor`` objects are converted to NumPy - arrays before pickling. + This is used to check if an object that is to be pickled is a :py:class:`~torch.Tensor` or not, because PyTorch :py:class:`~torch.Tensor` objects + are converted to :py:class:`~numpy.ndarray` before pickling. """ @@ -68,10 +71,10 @@ def write(self, data: bytes) -> int: """Updates the hash, by adding the specified data to the end of the input. Note: - This method was made to give the ``Hasher`` object a file-like interface. + This method was made to give the ``MetroHash128`` object a file-like interface. Args: - data (bytes): The data to add to the hash. This can be any bytes-like object. + data (bytes): The data to add to the hash. This can be any :py:class:`bytes`-like object. Returns: int: Returns the number of bytes added to the hash. @@ -85,16 +88,16 @@ class HashPickler(pickle.Pickler): """A pickler for computing hashes.""" @staticmethod - def numpy_id(array: NDArray[Any]) -> tuple[str, tuple[int, ...], bytes, bytes]: - """Computes a unique ID for NumPy arrays, which consists of the data type name, the array's shape, and the values of the array decomposed into - their respective mantissas and exponents as a ``bytes`` sequence. + def numpy_id(array: numpy.ndarray[typing.Any, typing.Any]) -> tuple[str, tuple[int, ...], bytes, bytes]: + """Computes a unique ID for a :py:class:`~numpy.ndarray`, which consists of the data type name, the array's shape, and the values of the array + decomposed into their respective mantissas and exponents as a :py:class:`bytes` sequence. Args: - array (NDArray[Any]): The NumPy array to compute the ID for. + array (numpy.ndarray[typing.Any, typing.Any]): The :py:class:`~numpy.ndarray` to compute the ID for. Returns: - tuple[str, tuple[int, ...], bytes, bytes]: A tuple containing the data type name, the array's shape, and the values of the array - decomposed into their respective mantissas and exponents as a ``bytes`` sequence. + tuple[str, tuple[int, ...], bytes, bytes]: Returns a tuple containing the data type name, the array's shape, and the values of the array + decomposed into their respective mantissas and exponents as a :py:class:`bytes` sequence. """ mantissa, exponent = numpy.frexp(array) @@ -106,19 +109,19 @@ def numpy_id(array: NDArray[Any]) -> tuple[str, tuple[int, ...], bytes, bytes]: bytes(exponent), ) - def persistent_id(self, obj: Any) -> tuple[str, tuple[int, ...], bytes, bytes] | None: - """Computes a persistent ID for an object that is to be pickled, which can be used by the ``pickle`` module to identify two objects as "the - same" during the un-pickling process. The persistent ID is used to identify the object in a way that is independent of its memory address. - This is useful for caching and serialization purposes. + def persistent_id(self, obj: typing.Any) -> tuple[str, tuple[int, ...], bytes, bytes] | None: + """Computes a persistent ID for an object that is to be pickled, which can be used by the :py:mod:`pickle` module to identify two objects as + "the same" during the un-pickling process. The persistent ID is used to identify the object in a way that is independent of its memory + address. This is useful for caching and serialization purposes. Args: - obj (Any): The object to compute the persistent ID for. + obj (typing.Any): The object to compute the persistent ID for. Returns: - tuple[str, tuple[int, ...], bytes, bytes] | None: Returns a persistent ID for the object. If the object is a NumPy array, it returns a - tuple containing the data type name, the array's shape, and the values of the array decomposed into their respective mantissas and - exponents as a ``bytes`` sequence. If the object is a PyTorch tensor, it converts the tensor to a NumPy array and computes a unique ID - for the array. If the object is neither, it returns ``None``. + tuple[str, tuple[int, ...], bytes, bytes] | None: Returns a persistent ID for the object. If the object is a :py:class:`~numpy.ndarray`, + it returns a tuple containing the data type name, the array's shape, and the values of the array decomposed into their respective + mantissas and exponents as a :py:class:`bytes` sequence. If the object is a :py:class:`~torch.Tensor`, it converts the tensor to a + :py:class:`~numpy.ndarray` and computes a unique ID for the array. If the object is neither, it returns :py:obj:`None`. """ if isinstance(obj, ndarray): @@ -128,15 +131,16 @@ def persistent_id(self, obj: Any) -> tuple[str, tuple[int, ...], bytes, bytes] | return None -def ext_hash(data: Any) -> str: +def ext_hash(data: typing.Any) -> str: """Hashes the specified data. It uses an extended, non-cryptographic hashing algorithm, which first pickles the specified object and then hashes - the resulting ``bytes`` sequence using MetroHash. + the resulting :py:class:`bytes` sequence using MetroHash. Args: - data (Any): The data to hash. This can be any Python object, including NumPy arrays and PyTorch tensors. + data (typing.Any): The data to hash. This can be any Python :py:class:`object`, including :py:class:`~numpy.ndarray` and + :py:class:`~torch.Tensor`. Returns: - str: Returns the hash of the data as a hexadecimal string. + str: Returns the hash of the data as a hexadecimal :py:class:`str`. """ hasher = Hasher() diff --git a/source/corelay/io/storage.py b/source/corelay/io/storage.py index da13f3f..f18bf13 100644 --- a/source/corelay/io/storage.py +++ b/source/corelay/io/storage.py @@ -1,18 +1,20 @@ -"""A module that contains classes to read and write different file formats like HDF5.""" +"""A module that contains classes to read and write intermediate results of operations performed by instances of +:class:`~corelay.processor.base.Processor` to and from different file formats like HDF5 and Python pickles. +""" +import collections +import collections.abc import copy import json +import pathlib import pickle +import types +import typing from abc import ABC, abstractmethod -from collections import OrderedDict -from collections.abc import KeysView -from os import PathLike -from types import TracebackType -from typing import Annotated, Any, IO, Literal, NamedTuple, Protocol, runtime_checkable +from typing import Annotated, IO, NamedTuple, Protocol, runtime_checkable import h5py import numpy -from numpy.typing import NDArray from corelay.base import Param from corelay.io.hashing import ext_hash @@ -21,28 +23,30 @@ @runtime_checkable class Storable(Protocol): - """An abstract class that defines the interface for storable objects, i.e., objects that have a ``read`` and ``write`` method.""" + """An abstract class that defines the interface for storable objects, i.e., objects that have a :py:meth:`~Storable.read` and + :py:meth:`~Storable.write` method. + """ - def read(self, data_in: Any, meta: Any) -> Any: - """Retrieves the output data that was produced by the specified input data if it is available. The meta data can contain additional + def read(self, data_in: typing.Any, meta: typing.Any) -> typing.Any: + """Retrieves the output data that was produced by the specified input data if it is available. The metadata can contain additional identifying information about the data. Args: - data_in (Any): The input data to retrieve the output data for. - meta (Any): The meta data to retrieve the output data for, which can contain additional identifying information about the data. + data_in (typing.Any): The input data to retrieve the output data for. + meta (typing.Any): The metadata to retrieve the output data for, which can contain additional identifying information about the data. Returns: - Any: The output data that was produced by the specified input data if it is available. + typing.Any: Returns the output data that was produced by the specified input data if it is available. """ - def write(self, data_out: Any, data_in: Any, meta: Any) -> None: - """Writes the specified output data to the storage container. The meta data that can be used to store additional identifying information about + def write(self, data_out: typing.Any, data_in: typing.Any, meta: typing.Any) -> None: + """Writes the specified output data to the storage container. The metadata that can be used to store additional identifying information about the data. Args: - data_out (Any): The output data to write. - data_in (Any): The input data that produced the output data. - meta (Any): The meta data that can be used to store additional identifying information about the data. + data_out (typing.Any): The output data to write. + data_in (typing.Any): The input data that produced the output data. + meta (typing.Any): The metadata that can be used to store additional identifying information about the data. """ @@ -50,10 +54,10 @@ class NoDataSource(Exception): """An exception, which is raised when no data source available.""" def __init__(self, message: str = 'No Data Source available.') -> None: - """Initializes a new ``NoDataSource`` instance. + """Initializes a new :py:class:`NoDataSource` instance. Args: - message (str): The error message to be displayed. Defaults to 'No Data Source available.'. + message (str): The error message to be displayed. Defaults to "No Data Source available." """ super().__init__(message) @@ -63,33 +67,34 @@ class NoDataTarget(Exception): """An exception, which is raised when no target source available.""" def __init__(self) -> None: - """Initializes a new ``NoDataTarget`` instance.""" + """Initializes a new :py:class:`NoDataTarget` instance.""" super().__init__('No Data Target available.') -RecursiveNDArrayTuple = tuple['NDArray[Any] | RecursiveNDArrayTuple', ...] -"""A recursive tuple of NumPy arrays, i.e., a tuple that contains NumPy arrays or other tuples of NumPy arrays, which themselves can contain other -tuples of NumPy arrays, and so on. This is used to represent a nested structure of NumPy arrays. +RecursiveNumPyArrayTuple = tuple['numpy.ndarray[typing.Any, typing.Any] | RecursiveNumPyArrayTuple', ...] +"""A recursive tuple of :py:class:`~numpy.ndarray`, i.e., a tuple that contains :py:class:`~numpy.ndarray` or other tuples of +:py:class:`~numpy.ndarray`, which themselves can contain other tuples of :py:class:`~numpy.ndarray`, and so on. This is used to represent a nested +structure of :py:class:`~numpy.ndarray`. """ RecursiveHashTuple = tuple['str | RecursiveHashTuple', ...] """A recursive tuple of strings, i.e., a tuple that contains strings or other tuples of strings, which themselves can contain other tuples of strings, -and so on. This is used to represent a nested structure of hashes for the data that is stored in a ``RecursiveNDArrayTuple``. +and so on. This is used to represent a nested structure of hashes for the data that is stored in a :py:class:`RecursiveNumPyArrayTuple`. """ class HashedHDF5: - """A storage container, which can be used to store ``Processor`` data in HDF5 files. A hash of the input data that produced the stored data is - stored alongside the data, so that the data can later be retrieved based on the input data. + """A storage container, which can be used to store :py:class:`~corelay.processor.base.Processor` data in HDF5 files. A hash of the input data that + produced the stored data is stored alongside the data, so that the data can later be retrieved based on the input data. """ base: h5py.Group """The HDF5 group to store the data in.""" def __init__(self, h5group: h5py.Group) -> None: - """Initializes a new ``HashedHDF5`` instance. + """Initializes a new :py:class:`HashedHDF5` instance. Args: h5group (h5py.Group): The HDF5 group to store the data in. @@ -97,19 +102,19 @@ def __init__(self, h5group: h5py.Group) -> None: self.base = h5group - def read(self, data_in: Any, meta: Any) -> Any: + def read(self, data_in: typing.Any, meta: typing.Any) -> typing.Any: """Retrieves the output data that was produced by the specified input data if it is available. The hash is computed from the input data and - the meta data. The meta data can contain additional identifying information about the data. + the metadata. The metadata can contain additional identifying information about the data. Args: - data_in (Any): The input data to retrieve the output data for. - meta (Any): The meta data to retrieve the output data for, which can contain additional identifying information about the data. + data_in (typing.Any): The input data to retrieve the output data for. + meta (typing.Any): The metadata to retrieve the output data for, which can contain additional identifying information about the data. Raises: NoDataSource: The data source is not available. Returns: - Any: The output data that was produced by the specified input data if it is available. + typing.Any: Returns the output data that was produced by the specified input data if it is available. """ hash_value = ext_hash((data_in, meta)) @@ -120,14 +125,14 @@ def read(self, data_in: Any, meta: Any) -> Any: return HashedHDF5._read_hdf5_content_recursively(group['data']) - def write(self, data_out: Any, data_in: Any, meta: Any) -> None: - """Writes the specified output data to a hashed HDF5 group. The hash is computed from the input data and the meta data. The meta data that can + def write(self, data_out: typing.Any, data_in: typing.Any, meta: typing.Any) -> None: + """Writes the specified output data to a hashed HDF5 group. The hash is computed from the input data and the metadata. The metadata that can be used to store additional identifying information about the data. Args: - data_out (Any): The output data to write. - data_in (Any): The input data that produced the output data. Is used to compute the hash. - meta (Any): The meta data that can be used to store additional identifying information about the data. Is used to compute the hash. + data_out (typing.Any): The output data to write. + data_in (typing.Any): The input data that produced the output data. Is used to compute the hash. + meta (typing.Any): The metadata that can be used to store additional identifying information about the data. Is used to compute the hash. Raises: TypeError: The data type of the input data is not supported. @@ -149,7 +154,7 @@ def write(self, data_out: Any, data_in: Any, meta: Any) -> None: group['output'] = json.dumps(HashedHDF5._hash_data_recursively(data_out)) @staticmethod - def _read_hdf5_content_recursively(base: h5py.Group | h5py.Dataset) -> NDArray[Any] | RecursiveNDArrayTuple: + def _read_hdf5_content_recursively(base: h5py.Group | h5py.Dataset) -> numpy.ndarray[typing.Any, typing.Any] | RecursiveNumPyArrayTuple: """Recursively goes through the hierarchy of HDF5 groups and converts the HDF5 datasets (which are the leaf nodes in this hierarchy) into a hierarchy of nested tuples. The keys of the groups are sorted to ensure that the order of the data is consistent. @@ -160,32 +165,34 @@ def _read_hdf5_content_recursively(base: h5py.Group | h5py.Dataset) -> NDArray[A TypeError: The data type of the input data is not supported. Returns: - NDArray[Any] | RecursiveNDArrayTuple: Returns a tuple of NumPy arrays or other tuples of NumPy arrays, which themselves can be nested. - Each tuple represents an HDF5 group. Nested tuples appear in the alphabetical order of the keys of the corresponding HDF5 groups. - The NumPy arrays contain the data that was read from the HDF5 datasets contained as leaf nodes in the HDF5 groups. If the - specified ``base`` is a dataset, the data is directly returned as a NumPy array. + numpy.ndarray[typing.Any, typing.Any] | RecursiveNumPyArrayTuple: Returns a tuple of :py:class:`~numpy.ndarray` or other tuples of + :py:class:`~numpy.ndarray`, which themselves can be nested. Each tuple represents an HDF5 group. Nested tuples appear in the alphabetical + order of the keys of the corresponding HDF5 groups. The :py:class:`~numpy.ndarray` instances contain the data that was read from the HDF5 + datasets contained as leaf nodes in the HDF5 groups. If the specified ``base`` is a dataset, the data is directly returned as a + :py:class:`~numpy.ndarray`. """ if isinstance(base, h5py.Group): return tuple(HashedHDF5._read_hdf5_content_recursively(base[key]) for key in sorted(base)) if isinstance(base, h5py.Dataset): - dataset_data: NDArray[Any] = base[()] + dataset_data: numpy.ndarray[typing.Any, typing.Any] = base[()] return dataset_data raise TypeError('Unsupported output type!') @staticmethod - def _write_hdf5_recursively(data: RecursiveNDArrayTuple | NDArray[Any], group: h5py.Group, key: str) -> None: - """Recursively writes the specified data to an HDF5 group from a tuple hierarchy of NumPy arrays. The keys of the group are generated from - the indices of the content in the tuples. The NumPy arrays are stored as HDF5 datasets. + def _write_hdf5_recursively(data: RecursiveNumPyArrayTuple | numpy.ndarray[typing.Any, typing.Any], group: h5py.Group, key: str) -> None: + """Recursively writes the specified data to an HDF5 group from a tuple hierarchy of :py:class:`~numpy.ndarray`. The keys of the group are + generated from the indices of the content in the tuples. The :py:class:`~numpy.ndarray` instances are stored as HDF5 datasets. Args: - data (RecursiveNDArrayTuple | NDArray[Any]): The data to write to the HDF5 group, which can either be a tuple containing NumPy arrays or - other tuples containing NumPy arrays or tuples of NumPy arrays, or a NumPy array. + data (RecursiveNumPyArrayTuple | numpy.ndarray[typing.Any, typing.Any]): The data to write to the HDF5 group, which can either be a tuple + containing instances of :py:class:`~numpy.ndarray` or other tuples containing instances of :py:class:`~numpy.ndarray` or tuples of + instances of :py:class:`~numpy.ndarray`, or a :py:class:`~numpy.ndarray`. group (h5py.Group): The HDF5 group to write the data to. key (str): The key of the HDF5 group to write the data to. If the data is a tuple, the key is used to create a new group in the HDF5 parent ``group``. The children of the tuple will then be written recursively to the newly created group and their key will be - generated using the index of the child inside of the tuple. If the data is a NumPy array, the key is used to create a new dataset - in the HDF5 parent ``group``. + generated using the index of the child inside of the tuple. If the data is a :py:class:`~numpy.ndarray`, the key is used to create a + new dataset in the HDF5 parent ``group``. Raises: TypeError: The data type of the input data is not supported. @@ -204,17 +211,17 @@ def _write_hdf5_recursively(data: RecursiveNDArrayTuple | NDArray[Any], group: h ) @staticmethod - def _hash_data_recursively(data: tuple[Any, ...] | Any) -> str | RecursiveHashTuple: + def _hash_data_recursively(data: tuple[typing.Any, ...] | typing.Any) -> str | RecursiveHashTuple: """Hashes the specified data recursively. If the data is a tuple, then the elements of the tuple are hashed recursively and the resulting hashes are returned as a tuple. For all other data types, the hash is computed directly and returned. Args: - data (tuple[Any, ...] | Any): The data to hash. This can either be a tuple containing multiple elements, which themselves can be - tuples containing the data in a hierarchy, or it can be a single element. + data (tuple[typing.Any, ...] | typing.Any): The data to hash. This can either be a tuple containing multiple elements, which themselves + can be tuples containing the data in a hierarchy, or it can be a single element. Returns: - str | RecursiveHashTuple: The hash of the data. If the data is a tuple, then the hashes of the elements are returned as a tuple. For - all other data types, the hash is computed directly and returned as a string. + str | RecursiveHashTuple: Returns the hash of the data. If the data is a tuple, then the hashes of the elements are returned as a tuple. + For all other data types, the hash is computed directly and returned as a :py:class:`str`. """ if isinstance(data, tuple): @@ -225,45 +232,48 @@ def _hash_data_recursively(data: tuple[Any, ...] | Any) -> str | RecursiveHashTu class DataStorageBase(ABC, Plugboard): """The abstract base class for key-value stores.""" - io: Any - """The storage object to read and write data to. Defaults to `None`.""" + io: typing.Any + """The storage object to read and write data to. Defaults to :py:obj:`None`.""" - def __init__(self, **kwargs: Any) -> None: - """Initializes a new ``DataStorageBase`` instance. + def __init__(self, **kwargs: typing.Any) -> None: + """Initializes a new :py:class:`DataStorageBase` instance. Args: - **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., ``Plugboard``. + **kwargs (typing.Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., + :py:class:`~corelay.plugboard.Plugboard`. """ super().__init__(**kwargs) - self.io: Any = None + self.io: typing.Any = None @abstractmethod - def read(self, data_in: Any = None, meta: Any = None) -> Any: - """Reads the output data that was produced by the specified input data, if it is available. The meta data can contain additional identifying + def read(self, data_in: typing.Any = None, meta: typing.Any = None) -> typing.Any: + """Reads the output data that was produced by the specified input data, if it is available. The metadata can contain additional identifying information about the data. Args: - data_in (Any, optional): Input data that produces the data that is to be read. Defaults to `None`. - meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + data_in (typing.Any): Input data that produces the data that is to be read. Defaults to :py:obj:`None`. + meta (typing.Any): Meta data that contains additional identifying information about the data that is to be read. Defaults to + :py:obj:`None`. Raises: NoDataSource: The data source is not available. Returns: - Any: Returns the data that was produced by the specified input data if it is available. + typing.Any: Returns the data that was produced by the specified input data if it is available. """ @abstractmethod - def write(self, data_out: Any, data_in: Any = None, meta: Any = None) -> None: - """Writes the specified output data to the storage. The hash is computed from the input data and the meta data. The meta data can be used to + def write(self, data_out: typing.Any, data_in: typing.Any = None, meta: typing.Any = None) -> None: + """Writes the specified output data to the storage. The hash is computed from the input data and the metadata. The metadata can be used to store additional identifying information about the data. Args: - data_out (Any): The output data to write. - data_in (Any, optional): The input data that produced the output data. Defaults to `None`. - meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. + data_out (typing.Any): The output data to write. + data_in (typing.Any): The input data that produced the output data. Defaults to :py:obj:`None`. + meta (typing.Any): The metadata that can be used to store additional identifying information about the data. Defaults to + :py:obj:`None`. """ @abstractmethod @@ -271,15 +281,15 @@ def exists(self) -> bool: """Checks if the data if data exists. Returns: - bool: Returns `True` if the data exists and `False` otherwise. + bool: Returns :py:obj:`True` if the data exists and :py:obj:`False` otherwise. """ @abstractmethod - def keys(self) -> KeysView[str]: + def keys(self) -> collections.abc.KeysView[str]: """Retrieves the keys of the data stored in the storage container. Returns: - KeysView[str]: Returns a list of keys of the io file object. + collections.abc.KeysView[str]: Returns a list of keys of the IO file object. """ def __enter__(self) -> 'DataStorageBase': @@ -288,23 +298,23 @@ def __enter__(self) -> 'DataStorageBase': and resources are released when the context manager exits. Returns: - DataStorageBase: Returns this instance of the ``DataStorageBase`` class. + DataStorageBase: Returns this instance of the :py:class:`DataStorageBase` class. """ return self - def __exit__(self, exception_type: type[Exception] | None, exception: Exception, traceback: TracebackType | None) -> None: + def __exit__(self, exception_type: type[Exception] | None, exception: Exception, traceback: types.TracebackType | None) -> None: """Closes the IO object. This is used to implement the context manager protocol, which allows the use of the `with` statement to automatically close the IO object when it is no longer needed. This is useful for ensuring that the IO object is properly closed and resources are released when the context manager exits. Args: exception_type (type[Exception] | None): When the context manager exits due to an exception, this is the type of the exception that was - raised, otherwise it is ``None``. + raised, otherwise it is :py:obj:`None`. exception (Exception): When the context manager exits due to an exception, this is the exception that was raised, otherwise it is - ``None``. - traceback (TracebackType | None): When the context manager exits due to an exception, this is the traceback of the exception that was - raised, otherwise it is ``None``. + :py:obj:`None`. + traceback (types.TracebackType | None): When the context manager exits due to an exception, this is the traceback of the exception that + was raised, otherwise it is :py:obj:`None`. """ if self.io is not None: @@ -317,10 +327,10 @@ def __contains__(self, key: str) -> bool: key (str): The key to check for existence. Raises: - TypeError: The key is not a string. + TypeError: The key is not a :py:class:`str`. Returns: - bool: Returns 'True' if the key exists in the storage and `False` otherwise. + bool: Returns 'True' if the key exists in the storage and :py:obj:`False` otherwise. """ if not isinstance(key, str): @@ -328,17 +338,17 @@ def __contains__(self, key: str) -> bool: return self.at(data_key=key).exists() - def __getitem__(self, key: str) -> Any: - """Get the data for a given key. + def __getitem__(self, key: str) -> typing.Any: + """Gets the data for a given key. Args: key (str): The key to get the data for. Raises: - TypeError: The key is not a string. + TypeError: The key is not a :py:class:`str`. Returns: - Any: Returns the data for the given key. + typing.Any: Returns the data for the given key. """ if not isinstance(key, str): @@ -346,15 +356,15 @@ def __getitem__(self, key: str) -> Any: return self.at(data_key=key).read() - def __setitem__(self, key: str, value: Any) -> None: - """Set the data for a given key. + def __setitem__(self, key: str, value: typing.Any) -> None: + """Sets the data for a given key. Args: key (str): The key to set the data for. - value (Any): The data to set for the given key. + value (typing.Any): The data to set for the given key. Raises: - TypeError: The key is not a string. + TypeError: The key is not a :py:class:`str`. """ if not isinstance(key, str): @@ -363,33 +373,33 @@ def __setitem__(self, key: str, value: Any) -> None: return self.at(data_key=key).write(value) def __bool__(self) -> bool: - """Converts the data storage object to a boolean value. This is used to determine if the data storage object is actually backed by a store. + """Converts the data storage object to a :py:class:`bool` value. This is used to determine if the data storage object is actually backed by a + store. Returns: - bool: Returns `True` if the data storage object is backed by a store and `False` otherwise. + bool: Returns :py:obj:`True` if the data storage object is backed by a store and :py:obj:`False` otherwise. """ return bool(self.io) def close(self) -> None: - """Close opened io file object.""" + """Close opened IO file object.""" if self.io is not None: self.io.close() - def at(self, **kwargs: Any) -> 'DataStorageBase': + def at(self, **kwargs: typing.Any) -> 'DataStorageBase': """Returns a copy of the instance where the keyword arguments were added as attributes of the class become the attributes of the class. Args: - **kwargs (Any): The keyword arguments, which are added as attributes of the class. + **kwargs (typing.Any): The keyword arguments, which are added as attributes of the class. Raises: TypeError: One or more of the names in the keyword arguments are not valid attribute names. Returns: DataStorageBase: Returns a copy of the instance where the keyword arguments were added as attributes of the class become the attributes - of the class. This allows to create a new instance of the class with new or updated attributes without modifying the original - instance. + of the class. This allows to create a new instance of the class with new or updated attributes without modifying the original instance. """ result = copy.copy(self) @@ -407,39 +417,42 @@ class NoStorage(DataStorageBase): """ def __bool__(self) -> bool: - """Converts the data storage object to a boolean value. This is used to determine if the data storage object is actually backed by a store. + """Converts the data storage object to a :py:class:`bool` value. This is used to determine if the data storage object is actually backed by a + store. Returns: - bool: Returns `False` since this is a placeholder data storage class and does not actually use persistent storage. + bool: Returns :py:obj:`False` since this is a placeholder data storage class and does not actually use persistent storage. """ return False - def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable=unused-argument - """Reads the output data that was produced by the specified input data, if it is available. The meta data can contain additional identifying + def read(self, data_in: typing.Any = None, meta: typing.Any = None) -> typing.Any: # pylint: disable=unused-argument + """Reads the output data that was produced by the specified input data, if it is available. The metadata can contain additional identifying information about the data. Args: - data_in (Any, optional): Input data that produces the data that is to be read. Defaults to `None`. - meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + data_in (typing.Any): Input data that produces the data that is to be read. Defaults to :py:obj:`None`. + meta (typing.Any): Meta data that contains additional identifying information about the data that is to be read. Defaults to + :py:obj:`None`. Raises: NoDataSource: This is a placeholder data storage class and does not actually use persistent storage and therefore always raises this exception. Returns: - Any: The data that was produced by the specified input data if it is available. + typing.Any: Returns the data that was produced by the specified input data if it is available. """ raise NoDataSource() - def write(self, data_out: Any, data_in: Any = None, meta: Any = None) -> None: # pylint: disable=unused-argument - """Writes the specified output data to the storage. The meta data can be used to store additional identifying information about the data. + def write(self, data_out: typing.Any, data_in: typing.Any = None, meta: typing.Any = None) -> None: # pylint: disable=unused-argument + """Writes the specified output data to the storage. The metadata can be used to store additional identifying information about the data. Args: - data_out (Any): The output data to write. - data_in (Any, optional): The input data that produced the output data. Defaults to `None`. - meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. + data_out (typing.Any): The output data to write. + data_in (typing.Any): The input data that produced the output data. Defaults to :py:obj:`None`. + meta (typing.Any): The metadata that can be used to store additional identifying information about the data. Defaults to + :py:obj:`None`. Raises: NoDataTarget: This is a placeholder data storage class and does not actually use persistent storage and therefore always raises this @@ -456,12 +469,12 @@ def exists(self) -> bool: exception. Returns: - bool: Returns `False` since this is a placeholder data storage class and does not actually use persistent storage. + bool: Returns :py:obj:`False` since this is a placeholder data storage class and does not actually use persistent storage. """ raise NoDataSource() - def keys(self) -> KeysView[str]: + def keys(self) -> collections.abc.KeysView[str]: """Retrieves the keys of the data stored in the storage container. Raises: @@ -469,50 +482,42 @@ def keys(self) -> KeysView[str]: exception. Returns: - KeysView[str]: Returns never, since this is a placeholder data storage class that does not actually use persistent storage and raises an - exception. + collections.abc.KeysView[str]: Returns never, since this is a placeholder data storage class that does not actually use persistent storage + and raises an exception. """ raise NoDataSource() -FileOpenMode = Literal['w', 'r', 'a'] -"""The file open mode to use when opening a file. The options are: - -- "w": Write mode. The file is created if it does not exist and existing files will be overwritten. -- "r": Read mode. The file must already exist and the data is read from the file. -- "a": Append mode. The file is created if it does not exist and the data is appended to the end of the file if the file already exists. -""" - - class PickleStorage(DataStorageBase): - """Experimental pickle storage that uses the ``pickle`` module to store data.""" + """Experimental pickle storage that uses the :py:mod:`pickle` module to store data.""" - io: IO[Any] + io: IO[typing.Any] """The file object to read data from and write data to. This is a binary file object that is used to store the pickled data.""" - data: dict[str, Any] - """A dictionary that stores the data that is read from or written to the file. The keys of the dictionary are the keys of the data that is stored - in the file, and the values are the data that is stored in the file. The dictionary is used to cache the data that is read from the file, so - that it does not need to be read from the file again if it is already cached. + data: dict[str, typing.Any] + """A :py:class:`dict` that stores the data that is read from or written to the file. The keys of the :py:class:`dict` are the keys of the data + that is stored in the file, and the values are the data that is stored in the file. The :py:class:`dict` is used to cache the data that is read + from the file, so that it does not need to be read from the file again if it is already cached. """ - data_key: Annotated[str, Param(str, 'data', mandatory=True)] + data_key: Annotated[str, Param(str, mandatory=True)] """The key of the data that is read from the pickle file or written to the pickle file.""" - def __init__(self, path: str | PathLike[str], mode: FileOpenMode = 'r', data_key: str | None = None, **kwargs: Any) -> None: + def __init__(self, path: str | pathlib.Path, mode: str = 'r', data_key: str | None = None, **kwargs: typing.Any) -> None: """ - Initializes a new ``PickleStorage`` instance. + Initializes a new :py:class:`PickleStorage` instance. Args: - path (str | PathLike[str]): The path to the pickle file where the data is to read from or written to. - mode (FileOpenMode, optional): The mode in which the file is opened. This can be either "w" for write mode, "r" for read mode or "a" for - append mode. In write mode, the file is created if it does not exist and the existing file is overwritten. In read mode, the file must - already exist and the data is read from the file. In append mode, the file is created if it does not exist and the data is appended to - the end of the file. Defaults to "r". - data_key (str | None): The key of the data that is read from the pickle file or written to the pickle file. Defaults to `None`. - **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., - ``DataStorageBase``. + path (str | pathlib.Path): The path to the pickle file where the data is to read from or written to. + mode (str): The mode in which the file is opened. This can be either "w" for write mode, "r" for read mode or "a" for append mode. In + write mode, the file is created if it does not exist and the existing file is overwritten. In read mode, the file must already exist + and the data is read from the file. In append mode, the file is created if it does not exist and the data is appended to the end of + the file. Defaults to "r". + data_key (str | None): The key of the data that is read from the pickle file or written to the pickle file. Defaults to + :py:obj:`None`. + **kwargs (typing.Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., + :py:class:`DataStorageBase`. Raises: ValueError: The mode is not "w", "r", or "a". @@ -528,8 +533,8 @@ def __init__(self, path: str | PathLike[str], mode: FileOpenMode = 'r', data_key if mode not in ['w', 'r', 'a']: raise ValueError('Mode should be set to "w", "r", or "a".') - self.io: IO[Any] = open(path, mode + 'b') # pylint: disable=unspecified-encoding, consider-using-with - self.data: dict[str, Any] = {} + self.io: IO[typing.Any] = open(path, mode + 'b') # pylint: disable=unspecified-encoding, consider-using-with + self.data: dict[str, typing.Any] = {} def _load_data(self) -> None: """Loads the data from the pickle file into the data dictionary. This is done by reading the file until the end of the file is reached and @@ -544,18 +549,19 @@ def _load_data(self) -> None: except EOFError: pass - def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable=unused-argument + def read(self, data_in: typing.Any = None, meta: typing.Any = None) -> typing.Any: # pylint: disable=unused-argument """Retrieves the data for a given data key. Args: - data_in (Any, optional): Input data that produced the data that is to be read. Defaults to `None`. - meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + data_in (typing.Any): Input data that produced the data that is to be read. Defaults to :py:obj:`None`. + meta (typing.Any): Meta data that contains additional identifying information about the data that is to be read. Defaults to + :py:obj:`None`. Raises: NoDataSource: The data source for the given data key does not exist. Returns: - Any: Returns the data for the given data key. + typing.Any: Returns the data for the given data key. """ # The exists method will call keys which in turn will call _load_data, this way, the data will be lazy-loaded @@ -563,13 +569,14 @@ def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable raise NoDataSource(f"Key: '{self.data_key}' does not exist.") return self.data[self.data_key] - def write(self, data_out: Any, data_in: Any = None, meta: Any = None) -> None: + def write(self, data_out: typing.Any, data_in: typing.Any = None, meta: typing.Any = None) -> None: """Writes the specified output data to the pickle file using the given data key as: `{'data': data_out, 'key': self.data_key}`. Args: - data_out (Any): The data to write to the pickle file. - data_in (Any, optional): The input data that produced the output data. Defaults to `None`. - meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. + data_out (typing.Any): The data to write to the pickle file. + data_in (typing.Any): The input data that produced the output data. Defaults to :py:obj:`None`. + meta (typing.Any): The metadata that can be used to store additional identifying information about the data. Defaults to + :py:obj:`None`. """ self.data[self.data_key] = data_out @@ -579,17 +586,17 @@ def exists(self) -> bool: """Determines if the data key exists in the data. Returns: - bool: Returns `True` if the data key exists and `False` otherwise. + bool: Returns :py:obj:`True` if the data key exists and :py:obj:`False` otherwise. """ # The keys will call _load_data this way, the data will be lazy-loaded return self.data_key in self.keys() - def keys(self) -> KeysView[str]: + def keys(self) -> collections.abc.KeysView[str]: """Retrieves the keys of the data stored in the pickle file. Returns: - KeysView[str]: Returns a view of keys of the data that is stored in the file. + collections.abc.KeysView[str]: Returns a view of keys of the data that is stored in the file. """ if not self.data: @@ -598,15 +605,15 @@ def keys(self) -> KeysView[str]: class StringInfo(NamedTuple): - """A type for the type information that the ``h5py.check_string_dtype`` function returns. This class is, unfortunately, not exported by the + """A type for the type information that the :py:func:`h5py.check_string_dtype` function returns. This class is, unfortunately, not exported by the ``h5py`` module, so we have to define it ourselves to gain type safety. """ encoding: str - """The encoding of the string, e.g., "utf-8" or "ascii".""" + """The encoding of the :py:class:`str`, e.g., "utf-8" or "ascii".""" length: int - """The length of the string.""" + """The length of the :py:class:`str`.""" class HDF5Storage(DataStorageBase): @@ -618,18 +625,19 @@ class HDF5Storage(DataStorageBase): data_key: Annotated[str, Param(str, mandatory=True)] """The key of the data that is read from the HDF5 file or written to the HDF5 file.""" - def __init__(self, path: str | PathLike[str], mode: FileOpenMode = 'r', data_key: str | None = None, **kwargs: Any) -> None: - """Initializes a new ``HDF5Storage`` instance. + def __init__(self, path: str | pathlib.Path, mode: str = 'r', data_key: str | None = None, **kwargs: typing.Any) -> None: + """Initializes a new :py:class:`HDF5Storage` instance. Args: - path (str | PathLike[str]): The path to the HDF5 file where the data is to read from or written to. - mode (FileOpenMode, optional): The mode to open the HDF5 file in. This can be either "w" for write mode, "r" for read mode or "a" for - append mode. In write mode, the file is created if it does not exist and existing files will be overwritten. In read mode, the file - must already exist and the data is read from the file. In append mode, the file is created if it does not exist and the data is - appended to the end of the file if the file already exists. Defaults to "r". - data_key (str | None): The key of the data that is read from the HDF5 file or written to the HDF5 file. Defaults to `None`. - **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., - ``DataStorageBase``. + path (str | pathlib.Path): The path to the HDF5 file where the data is to read from or written to. + mode (str): The mode to open the HDF5 file in. This can be either "w" for write mode, "r" for read mode or "a" for append mode. In write + mode, the file is created if it does not exist and existing files will be overwritten. In read mode, the file must already exist and + the data is read from the file. In append mode, the file is created if it does not exist and the data is appended to the end of the + file if the file already exists. Defaults to "r". + data_key (str | None): The key of the data that is read from the HDF5 file or written to the HDF5 file. Defaults to + :py:obj:`None`. + **kwargs (typing.Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., + :py:class:`DataStorageBase`. """ # PyDocLint does not support the documentation of the constructor parameters both in the __init__ method and the class docstring, so we have @@ -641,18 +649,19 @@ def __init__(self, path: str | PathLike[str], mode: FileOpenMode = 'r', data_key self.io: h5py.File = h5py.File(path, mode=mode) - def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable=unused-argument + def read(self, data_in: typing.Any = None, meta: typing.Any = None) -> typing.Any: # pylint: disable=unused-argument """Retrieves the data for a given data key. Args: - data_in (Any, optional): Input data that produced the data that is to be read. Defaults to `None`. - meta (Any, optional): Meta data that contains additional identifying information about the data that is to be read. Defaults to `None`. + data_in (typing.Any): Input data that produced the data that is to be read. Defaults to :py:obj:`None`. + meta (typing.Any): Meta data that contains additional identifying information about the data that is to be read. Defaults to + :py:obj:`None`. Raises: NoDataSource: The data source for the given data key does not exist. Returns: - Any: Returns the data for the given data key. + typing.Any: Returns the data for the given data key. """ if not self.exists(): @@ -660,26 +669,33 @@ def read(self, data_in: Any = None, meta: Any = None) -> Any: # pylint: disable _, data = self._unpack('/', self.io[self.data_key]) return data - def write(self, data_out: dict[str, Any] | tuple[Any, ...] | Any, data_in: Any = None, meta: Any = None) -> None: - """Writes the specified output data to the HDF5 file. If the output data is a dictionary, then the output data is stored in an HDF5 group with - the name given by the data key. The key-value pairs of the dictionary will be stored in this HDF5 group with the keys of the dictionary used - as the names of the datasets and the values of the dictionary used as the data for the datasets. If the output data is a tuple, then the - output data is stored in an HDF5 group with the name given by the data key. The values of the tuple will be stored as datasets in this HDF5 - group, with the indices of the tuple used as the names of the datasets and the values of the tuple used as the data for the datasets. If the - output data is neither a dictionary nor a tuple, then the output data is stored in an HDF5 dataset with the name given by the data key and the - output data used as the data for the dataset. + def write( + self, + data_out: dict[str, typing.Any] | tuple[typing.Any, ...] | typing.Any, + data_in: typing.Any = None, + meta: typing.Any = None + ) -> None: + """Writes the specified output data to the HDF5 file. If the output data is a :py:class:`dict`, then the output data is stored in an HDF5 + group with the name given by the data key. The key-value pairs of the :py:class:`dict` will be stored in this HDF5 group with the keys of the + :py:class:`dict` used as the names of the datasets and the values of the :py:class:`dict` used as the data for the datasets. If the output + data is a tuple, then the output data is stored in an HDF5 group with the name given by the data key. The values of the tuple will be stored + as datasets in this HDF5 group, with the indices of the tuple used as the names of the datasets and the values of the tuple used as the data + for the datasets. If the output data is neither a :py:class:`dict` nor a tuple, then the output data is stored in an HDF5 dataset with the + name given by the data key and the output data used as the data for the dataset. Args: - data_out (dict[str, Any] | tuple[Any, ...] | Any): The data to write to the HDF5 file. This can either be a dataset, a tuple, or any value - that can be written to an HDF5 file (i.e., basic data types like ``int``, ``float``, ``bool``, or ``str``, or a NumPy array). If the - data is a dictionary, then it will be stored as an HDF5 group with the name given by the data key. The key-value pairs of the - dictionary will be stored in this HDF5 group with the keys of the dictionary used as the names of the datasets and the values of the - dictionary used as the data for the datasets. If the data is a tuple, then it will be stored as an HDF5 group with the name given by - the data key. The values of the tuple will be stored as datasets in this HDF5 group, with the indices of the tuple used as the names - of the datasets and the values of the tuple used as the data for the datasets. If the data is neither a dictionary nor a tuple, then - it will be stored in an HDF5 dataset with the name given by the data key and the data used as the data for the dataset. - data_in (Any, optional): The input data that produced the output data. Defaults to `None`. - meta (Any, optional): The meta data that can be used to store additional identifying information about the data. Defaults to `None`. + data_out (dict[str, typing.Any] | tuple[typing.Any, ...] | typing.Any): The data to write to the HDF5 file. This can either be a dataset, + a tuple, or any value that can be written to an HDF5 file (i.e., basic data types like :py:class:`int`, :py:class:`float`, + :py:class:`bool`, or :py:class:`str`, or a :py:class:`~numpy.ndarray`). If the data is a :py:class:`dict`, then it will be stored as + an HDF5 group with the name given by the data key. The key-value pairs of the :py:class:`dict` will be stored in this HDF5 group with + the keys of the :py:class:`dict` used as the names of the datasets and the values of the :py:class:`dict` used as the data for the + datasets. If the data is a tuple, then it will be stored as an HDF5 group with the name given by the data key. The values of the tuple + will be stored as datasets in this HDF5 group, with the indices of the tuple used as the names of the datasets and the values of the + tuple used as the data for the datasets. If the data is neither a :py:class:`dict` nor a tuple, then it will be stored in an HDF5 + dataset with the name given by the data key and the data used as the data for the dataset. + data_in (typing.Any): The input data that produced the output data. Defaults to :py:obj:`None`. + meta (typing.Any): The metadata that can be used to store additional identifying information about the data. Defaults to + :py:obj:`None`. """ if isinstance(data_out, dict): @@ -698,40 +714,41 @@ def exists(self) -> bool: """Checks if the data key exists in the HDF5 file. Returns: - bool: Returns `True` if the data key exists and `False` otherwise. + bool: Returns :py:obj:`True` if the data key exists and :py:obj:`False` otherwise. """ return self.data_key in self.io - def keys(self) -> KeysView[str]: + def keys(self) -> collections.abc.KeysView[str]: """Retrieves the keys of the data stored in the HDF5 file. Returns: - KeysView[str]: Returns a view of keys of the data in the HDF5 file. + collections.abc.KeysView[str]: Returns a view of keys of the data in the HDF5 file. """ - key_list: KeysView[str] = self.io.keys() + key_list: collections.abc.KeysView[str] = self.io.keys() return key_list @staticmethod - def _unpack(key: str | int, value: h5py.Dataset | h5py.Group | Any) -> tuple[str | int, Any]: - """Unpacks the specified value. If the value is an HDF5 dataset, it is converted to a NumPy array or a string, depending on the data type of - the dataset. If the value is a group, then it is recursively unpacked into an ordered dictionary, which contains the keys and values of the - group. The keys of the group are sorted to ensure that the order of the data is consistent. If the key is a string and only contains numeric - characters, it is converted to an integer. This is done to allow the use of the dictionary as a tuple or list. + def _unpack(key: str | int, value: h5py.Dataset | h5py.Group | typing.Any) -> tuple[str | int, typing.Any]: + """Unpacks the specified value. If the value is an HDF5 dataset, it is converted to a :py:class:`~numpy.ndarray` or a :py:class:`str`, + depending on the data type of the dataset. If the value is a group, then it is recursively unpacked into an + :py:class:`collections.OrderedDict`, which contains the keys and values of the group. The keys of the group are sorted to ensure that the + order of the data is consistent. If the key is a :py:class:`str` and only contains numeric characters, it is converted to an :py:class:`int`. + This is done to allow the use of the dictionary as a tuple or list. Args: - key (str | int): The key of the value to unpack. This can either be a string or an integer. If the key is a string and only contains - numeric characters, it is converted to an integer. - value (h5py.Dataset | h5py.Group | Any): The value that is to be unpacked. If the value is an HDF5 dataset, it is converted to a NumPy - array or a string, depending on the data type of the dataset. If the value is a group, then it is recursively unpacked into an ordered - dictionary, which contains the keys and values of the group. The keys of the group are sorted to ensure that the order of the data is - consistent. + key (str | int): The key of the value to unpack. This can either be a :py:class:`str` or an :py:class:`int`. If the key is a + :py:class:`str` and only contains numeric characters, it is converted to an :py:class:`int`. + value (h5py.Dataset | h5py.Group | typing.Any): The value that is to be unpacked. If the value is an HDF5 dataset, it is converted to a + NumPy array or a :py:class:`str`, depending on the data type of the dataset. If the value is a group, then it is recursively unpacked + into an :py:class:`collections.OrderedDict`, which contains the keys and values of the group. The keys of the group are sorted to + ensure that the order of the data is consistent. Returns: - tuple[str | int, Any]: Returns the unpacked key and value as a tuple. The key is either a string or an integer. The value is either a - NumPy array, a string, or an ordered dictionary, which contains the keys and values of the group. The keys of the group are sorted to - ensure that the order of the data is consistent. + tuple[str | int, typing.Any]: Returns the unpacked key and value as a tuple. The key is either a :py:class:`str` or an :py:class:`int`. + The value is either a :py:class:`~numpy.ndarray`, a :py:class:`str`, or an :py:class:`collections.OrderedDict`, which contains the keys + and values of the group. The keys of the group are sorted to ensure that the order of the data is consistent. """ # Converts the key to an integer if it is a string and only contains numeric characters @@ -749,23 +766,23 @@ def _unpack(key: str | int, value: h5py.Dataset | h5py.Group | Any) -> tuple[str # If the value is a group, it is converted to an ordered dictionary, which contains the keys and values of the group; since the numeric keys # are converted to an integer, the dictionary can be accessed like a tuple or list elif isinstance(value, h5py.Group): - value = OrderedDict((HDF5Storage._unpack(k, v) for k, v in value.items())) + value = collections.OrderedDict((HDF5Storage._unpack(k, v) for k, v in value.items())) # Returns the key and value as a tuple return key, value @staticmethod - def _get_shape_and_dtype(value: NDArray[Any] | str | int | float) -> tuple[tuple[int, ...], numpy.dtype[Any]]: + def _get_shape_and_dtype(value: numpy.ndarray[typing.Any, typing.Any] | str | int | float) -> tuple[tuple[int, ...], numpy.dtype[typing.Any]]: """Infers the shape and data type of the specified value. Args: - value (NDArray[Any] | str | int | float): The value for which to infer the shape and data type. This can be a NumPy array, a string, an - integer, or a float. + value (numpy.ndarray[typing.Any, typing.Any] | str | int | float): The value for which to infer the shape and data type. This can be a + :py:class:`~numpy.ndarray`, a :py:class:`str`, an :py:class:`int`, or a :py:class:`float`. Returns: - tuple[tuple[int, ...], numpy.dtype[Any]]: Returns a tuple containing the shape and data type of the specified value. The shape is a tuple - of integers representing the dimensions of the value. If the value is a NumPy array, the shape is the shape of the array. If the value - is of any other data type, the shape will be an empty tuple. + tuple[tuple[int, ...], numpy.dtype[typing.Any]]: Returns a tuple containing the shape and data type of the specified value. The shape is a + tuple of integers representing the dimensions of the value. If the value is a :py:class:`~numpy.ndarray`, the shape is the shape of the + array. If the value is of any other data type, the shape will be an empty tuple. """ if isinstance(value, numpy.ndarray): diff --git a/source/corelay/pipeline/__init__.py b/source/corelay/pipeline/__init__.py index 525ed0a..3864256 100644 --- a/source/corelay/pipeline/__init__.py +++ b/source/corelay/pipeline/__init__.py @@ -1 +1,9 @@ -"""A sub-package containing base and built-in pipelines.""" +"""A sub-package that contains pipelines, which perform a set of tasks represented by a special :py:class:`~corelay.plugboard.Slot` type called +:py:class:`~corelay.pipeline.base.Task`. :py:class:`~corelay.pipeline.base.Task` slots can be filled with a special +:py:class:`~corelay.plugboard.Plug` type called :py:class:`~corelay.pipeline.base.TaskPlug`, that ensure that the values they hold are instances of +:py:class:`~corelay.processor.base.Processor`. Furthermore, the sub-package contains :py:class:`~corelay.pipeline.base.Pipeline` implementations for +spectral embeddings, :py:class:`~corelay.pipeline.spectral.SpectralEmbedding`, and spectral clustering, +:py:class:`~corelay.pipeline.spectral.SpectralClustering`. These are specific to +`Spectral Relevance Analysis (SprAy) `_, an explainable artificial intelligence (XAI) method for +bridging the gap between local and global XAI. +""" diff --git a/source/corelay/pipeline/base.py b/source/corelay/pipeline/base.py index cdb4a7e..343730e 100644 --- a/source/corelay/pipeline/base.py +++ b/source/corelay/pipeline/base.py @@ -1,33 +1,37 @@ -"""A module that contains the base classes for tasks and pipeline.""" +"""A module that contains the base classes for pipelines, :py:class:`~corelay.pipeline.base.Pipeline`, and tasks of pipelines, +:py:class:`~corelay.pipeline.base.Task`, which are used to perform a specific set of operations on data. +""" -from collections import OrderedDict +import collections +import typing from collections.abc import Callable -from typing import Any from corelay.plugboard import Slot, Plug from corelay.processor.base import ensure_processor, Processor +from corelay.utils import get_fully_qualified_name class TaskPlug(Plug): - """A task plug, which ensures that all contained objects are processors.""" + """A task plug, which ensures that all contained objects are instances of :py:class:`~corelay.processor.base.Processor`.""" def __init__( self, slot: Slot, - obj: Processor | Callable[..., Any] | None = None, - default: Processor | Callable[..., Any] | None = None, - **kwargs: Any + obj: Processor | Callable[..., typing.Any] | None = None, + default: Processor | Callable[..., typing.Any] | None = None, + **kwargs: typing.Any ) -> None: - """Initializes a new ``TaskPlug`` instance. + """Initializes a new :py:class:`TaskPlug` instance. Args: - slot (Slot): Slot instance to associate with this ``TaskPlug``. - obj (Processor | Callable[..., Any] | None, optional): A processor held in the ``TaskPlug`` container. If not set, ``default`` is returned - as its value. Defaults to `None`. - default (Processor | Callable[..., Any] | None, optional): A plug-dependent lower-priority processor held in the ``TaskPlug`` container. - If not set, ``fallback`` is returned. Defaults to `None`. - **kwargs (Any): Keyword arguments passed down to the base class constructor, for cooperativity's sake, which is the next class in the - inheritance hierarchy. + slot (Slot): Slot instance to associate with this :py:class:`TaskPlug`. + obj (Processor | Callable[..., typing.Any] | None): A :py:class:`~corelay.processor.base.Processor` held in the :py:class:`TaskPlug` + container. If not set, :py:attr:`~TaskPlug.default` is returned as its value. Defaults to :py:obj:`None`. + default (Processor | Callable[..., typing.Any] | None): A plug-dependent lower-priority :py:class:`~corelay.processor.base.Processor` held + in the :py:class:`TaskPlug` container. If not set, :py:attr:`~corelay.plugboard.Plug.fallback` is returned. Defaults to + :py:obj:`None`. + **kwargs (typing.Any): Keyword arguments passed down to the base class constructor, for cooperativity's sake, which is the next class in + the inheritance hierarchy. """ if default is not None: @@ -39,36 +43,37 @@ def __init__( @property def obj(self) -> Processor | None: - """Gets or sets the processor contained in the ``TaskPlug``. If the ``TaskPlug`` does not contain a processor, ``default`` is retrieved - instead. - - Note: - Actually, only the setter of the ``obj`` property needs to be overridden. The proper way of overriding the setter of the ``obj`` property - is to use the `@Plug.obj.setter` idiom, but this, unfortunately, is detected as a false positive by MyPy. Although, they have promised to - support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used very often in - the wild. Their workaround solution is to also override the getter and then use the new getter to override the setter and the deleter. I - have tested it, only in the getter, `super().obj` can be used, as it is not, yet, overridden, but in the setter and deleter, the complete - function must be re-implemented. Also, when the getter is overridden, not only the setter, but also the deleter must be overridden, as - otherwise, it will not be available anymore. + """Gets or sets the :py:class:`~corelay.processor.base.Processor` contained in the :py:class:`TaskPlug`. If the :py:class:`TaskPlug` does not + contain a :py:class:`~corelay.processor.base.Processor`, :py:attr:`~TaskPlug.default` is retrieved instead. Returns: - Processor | None: Returns the processor contained in the ``TaskPlug``. If not set, ``default`` is returned. + Processor | None: Returns the :py:class:`~corelay.processor.base.Processor` contained in the :py:class:`TaskPlug`. If not set, + :py:attr:`~TaskPlug.default` is returned. """ + # Actually, only the setter of the obj property needs to be overridden; The proper way of overriding the setter of the obj property is to use + # the @Plug.obj.setter idiom, but this, unfortunately, is detected as a false positive by MyPy; although, they have promised to support this + # idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used very often in the wild; + # their workaround solution is to also override the getter and then use the new getter to override the setter and the deleter; I have tested + # it, only in the getter, super().obj can be used, as it is not, yet, overridden, but in the setter and deleter, the complete function must be + # re-implemented; also, when the getter is overridden, not only the setter, but also the deleter must be overridden, as otherwise, it will not + # be available anymore processor: Processor | None = super().obj return processor @obj.setter - def obj(self, value: Processor | Callable[..., Any] | None) -> None: - """Gets or sets the processor contained in the ``TaskPlug`` and checks for consistency. It is ensured first, that the value is a - ``Processor``. If not, it is converted to a ``Processor`` using the ``ensure_processor`` function. + def obj(self, value: Processor | Callable[..., typing.Any] | None) -> None: + """Gets or sets the :py:class:`~corelay.processor.base.Processor` contained in the :py:class:`TaskPlug` and checks for consistency. It is + ensured first, that the value is a :py:class:`~corelay.processor.base.Processor`. If not, it is converted to a + :py:class:`~corelay.processor.base.Processor` using the py:func:`ensure_processor` function. Args: - value (Processor | Callable[..., Any] | None): The processor to set. + value (Processor | Callable[..., typing.Any] | None): The :py:class:`~corelay.processor.base.Processor` to set. Raises: - TypeError: The processor is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The :py:class:`~corelay.processor.base.Processor` is not consistent with the :py:attr:`~corelay.plugboard.Plug.dtype`, i.e., it + is neither :py:obj:`None`, nor of the type :py:attr:`~corelay.plugboard.Plug.dtype` or one of the types in the tuple + :py:attr:`~corelay.plugboard.Plug.dtype`. """ if value is not None: @@ -82,41 +87,43 @@ def obj(self, value: Processor | Callable[..., Any] | None) -> None: @obj.deleter def obj(self) -> None: - """Deletes the processor contained in the ``TaskPlug`` by setting it to `None`.""" + """Deletes the :py:class:`~corelay.processor.base.Processor` contained in the :py:class:`TaskPlug` by setting it to :py:obj:`None`.""" self.obj = None @property - def default(self) -> Any: - """Gets or sets the default processor of the ``TaskPlug``. If the ``default`` processor is not set, then the ``fallback`` processor is - retrieved instead. - - Note: - Actually, only the setter of the ``default`` property needs to be overridden. The proper way of overriding the setter of the ``default`` - property is to use the `@Plug.default.setter` idiom, but this, unfortunately, is detected as a false positive by MyPy. Although, they have - promised to support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used - very often in the wild. Their workaround solution is to also override the getter and then use the new getter to override the setter and - the deleter. I have tested it, only in the getter, `super().default` can be used, as it is not, yet, overridden, but in the setter and - deleter, the complete function must be re-implemented. Also, when the getter is overridden, not only the setter, but also the deleter must - be overridden, as otherwise, it will not be available anymore. + def default(self) -> typing.Any: + """Gets or sets the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug`. If the :py:attr:`~TaskPlug.default` + :py:class:`~corelay.processor.base.Processor` is not set, then the :py:attr:`~corelay.plugboard.Plug.fallback` + :py:class:`~corelay.processor.base.Processor` is retrieved instead. Returns: - Any: Returns the default processor of the ``TaskPlug``. If not set, ``fallback`` is returned. + typing.Any: Returns the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug`. If not set, + :py:attr:`~corelay.plugboard.Plug.fallback` is returned. """ + # Actually, only the setter of the default property needs to be overridden; The proper way of overriding the setter of the default property is + # to use the @Plug.default.setter idiom, but this, unfortunately, is detected as a false positive by MyPy; although, they have promised to + # support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used very often in + # the wild; their workaround solution is to also override the getter and then use the new getter to override the setter and the deleter; I + # have tested it, only in the getter, super().default can be used, as it is not, yet, overridden, but in the setter and deleter, the complete + # function must be re-implemented; also, when the getter is overridden, not only the setter, but also the deleter must be overridden, as + # otherwise, it will not be available anymore return super().default @default.setter - def default(self, value: Any) -> None: - """Gets or sets the default processor of the ``TaskPlug`` and checks for consistency. It is ensured first, that the new default value is a - ``Processor``. If not, it is converted to a ``Processor`` using the ``ensure_processor`` function. + def default(self, value: typing.Any) -> None: + """Gets or sets the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug` and checks for consistency. It is + ensured first, that the new default value is a :py:class:`~corelay.processor.base.Processor`. If not, it is converted to a + :py:class:`~corelay.processor.base.Processor` using the py:func:`ensure_processor` function. Args: - value (Any): The new default processor to set. + value (typing.Any): The new default :py:class:`~corelay.processor.base.Processor` to set. Raises: - TypeError: The default processor is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The default :py:class:`~corelay.processor.base.Processor` is not consistent with the :py:attr:`~corelay.plugboard.Plug.dtype`, + i.e., it is neither :py:obj:`None`, nor of the type :py:attr:`~corelay.plugboard.Plug.dtype` or one of the types in the tuple + :py:attr:`~corelay.plugboard.Plug.dtype`. """ if value is not None: @@ -130,27 +137,35 @@ def default(self, value: Any) -> None: @default.deleter def default(self) -> None: - """Deletes the default processor of the ``TaskPlug`` by setting it to `None`.""" + """Deletes the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug` by setting it to :py:obj:`None`.""" self.default = None class Task(Slot): - """Represents a single task in a ``Pipeline``. Tasks are slots that ensure all contained objects are plugs and own default values that are - processors. + """Represents a single task in a :py:class:`Pipeline`. Tasks are slots that ensure all contained objects are plugs and own default values that are + instances of :py:class:`~corelay.processor.base.Processor`. """ - def __init__(self, proc_type: type[Processor] = Processor, default: Processor | Callable[..., Any] = lambda data: data, **kwargs: Any) -> None: - """Initializes a new ``Task`` instance. + def __init__( + self, + proc_type: type[Processor] = Processor, + default: Processor | Callable[..., typing.Any] = lambda data: data, + **kwargs: typing.Any + ) -> None: + """Initializes a new :py:class:`Task` instance. Args: - proc_type (type[Processor], optional): The type of ``Processor`` allowed for this ``Task``. Defaults to ``Processor``. - default (Processor | Callable[..., Any], optional): The default processor for the ``Task``, which must either be a ``Processor`` or a - function. Defaults to the identity function. - **kwargs (Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., ``Slot``. + proc_type (type[Processor]): The type of :py:class:`~corelay.processor.base.Processor` allowed for this :py:class:`Task`. Defaults to + :py:class:`~corelay.processor.base.Processor`. + default (Processor | Callable[..., typing.Any]): The default :py:class:`~corelay.processor.base.Processor` for the :py:class:`Task`, which + must either be a :py:class:`~corelay.processor.base.Processor` or a function. Defaults to the identity function. + **kwargs (typing.Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., + :py:class:`~corelay.plugboard.Slot`. Raises: - TypeError: The allowed ``Processor`` type for the ``Task``, ``proc_type``, is not of type ``Processor`` or a sub-class of ``Processor``. + TypeError: The allowed :py:class:`~corelay.processor.base.Processor` type for the :py:class:`Task`, ``proc_type``, is not of type + :py:class:`~corelay.processor.base.Processor` or a sub-class of :py:class:`~corelay.processor.base.Processor`. """ if not issubclass(proc_type, Processor): @@ -159,37 +174,52 @@ def __init__(self, proc_type: type[Processor] = Processor, default: Processor | default = ensure_processor(default, **kwargs) super().__init__(dtype=proc_type, default=default) + def __repr__(self) -> str: + """Returns a :py:class:`str` representation of the :py:class:`Task` instance. + + Returns: + str: Returns a :py:class:`str` representation of the :py:class:`Task` instance. + """ + + # Sphinx AutoDoc uses __repr__ when it encounters the metadata of typing.Annotated; this is a reasonable thing to do, but then it tries to + # resolve the resulting string as types for cross-referencing, which is not possible with the default implementation of __repr__; to be able + # to get proper documentation, the fully-qualified name of the class is returned, because this enable Sphinx AutoDoc to reference the class in + # the documentation + return f'~{get_fully_qualified_name(self)}' + @property def default(self) -> Processor | None: - """Gets or sets the default processor of the ``Task``. - - Note: - Actually, only the setter of the ``default`` property needs to be overridden. The proper way of overriding the setter of the ``default`` - property is to use the `@Slot.default.setter` idiom, but this, unfortunately, is detected as a false positive by MyPy. Although, they have - promised to support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used - very often in the wild. Their workaround solution is to also override the getter and then use the new getter to override the setter and - the deleter. I have tested it, only in the getter, `super().default` can be used, as it is not, yet, overridden, but in the setter and - deleter, the complete function must be re-implemented. Also, when the getter is overridden, not only the setter, but also the deleter must - be overridden, as otherwise, it will not be available anymore. + """Gets or sets the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`Task`. Returns: - Processor | None: Returns the task's default processor. If not set, `None` is returned. + Processor | None: Returns the task's default :py:class:`~corelay.processor.base.Processor`. If not set, :py:obj:`None` is returned. """ + # Actually, only the setter of the default property needs to be overridden; The proper way of overriding the setter of the default property is + # to use the @Plug.default.setter idiom, but this, unfortunately, is detected as a false positive by MyPy; although, they have promised to + # support this idiom (https://github.com/python/mypy/issues/5936), they have not done so yet, probably, because it is not used very often in + # the wild; their workaround solution is to also override the getter and then use the new getter to override the setter and the deleter; I + # have tested it, only in the getter, super().default can be used, as it is not, yet, overridden, but in the setter and deleter, the complete + # function must be re-implemented; also, when the getter is overridden, not only the setter, but also the deleter must be overridden, as + # otherwise, it will not be available anymore default_processor: Processor | None = super().default return default_processor @default.setter - def default(self, value: Processor | Callable[..., Any] | None) -> None: - """Gets or sets the default processor of the ``Task``. Checks the new default processor is a ``Processor``. If not, it is converted to a - ``Processor`` using the ``ensure_processor`` function. The default processor is checked for consistency with the ``dtype``. + def default(self, value: Processor | Callable[..., typing.Any] | None) -> None: + """Gets or sets the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`Task`. Checks the new default + :py:class:`~corelay.processor.base.Processor` is a :py:class:`~corelay.processor.base.Processor`. If not, it is converted to a + :py:class:`~corelay.processor.base.Processor` using the py:func:`ensure_processor` function. The default + :py:class:`~corelay.processor.base.Processor` is checked for consistency with the :py:attr:`~corelay.plugboard.Slot.dtype`. Args: - value (Processor | Callable[..., Any] | None): The new default processor to set. If not set, `None` is returned. + value (Processor | Callable[..., typing.Any] | None): The new default :py:class:`~corelay.processor.base.Processor` to set. If not set, + :py:obj:`None` is returned. Raises: - TypeError: The default processor is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The default :py:class:`~corelay.processor.base.Processor` is not consistent with the :py:attr:`~corelay.plugboard.Slot.dtype`, + i.e., it is neither :py:obj:`None`, nor of the type :py:attr:`~corelay.plugboard.Slot.dtype` or one of the types in the tuple + :py:attr:`~corelay.plugboard.Slot.dtype`. """ if value is not None: @@ -203,21 +233,25 @@ def default(self, value: Processor | Callable[..., Any] | None) -> None: @default.deleter def default(self) -> None: - """Deletes the task's default processor.""" + """Deletes the task's default :py:class:`~corelay.processor.base.Processor`.""" self._default = None - def __call__(self, obj: Processor | Callable[..., Any] | None = None, default: Processor | Callable[..., Any] | None = None) -> TaskPlug: - """Creates a new corresponding ``TaskPlug`` container. + def __call__( + self, + obj: Processor | Callable[..., typing.Any] | None = None, + default: Processor | Callable[..., typing.Any] | None = None + ) -> TaskPlug: + """Creates a new corresponding :py:class:`TaskPlug` container. Args: - obj (Processor | Callable[..., Any] | None, optional): A processor to initialize the newly created ``TaskPlug`` container's object value - to. Defaults to `None`. - default (Processor | Callable[..., Any] | None, optional): A processor to initialize the newly created ``TaskPlug`` container's default - value to. Defaults to `None`. + obj (Processor | Callable[..., typing.Any] | None): A :py:class:`~corelay.processor.base.Processor` to initialize the newly created + :py:class:`TaskPlug` container's object value to. Defaults to :py:obj:`None`. + default (Processor | Callable[..., typing.Any] | None): A :py:class:`~corelay.processor.base.Processor` to initialize the newly created + :py:class:`TaskPlug` container's default value to. Defaults to :py:obj:`None`. Returns: - TaskPlug: Returns the newly created ``TaskPlug`` container instance, obeying the type and optionality constraints. + TaskPlug: Returns the newly created :py:class:`TaskPlug` container instance, obeying the type and optionality constraints. """ return TaskPlug(self, obj=obj, default=default) @@ -226,17 +260,20 @@ def __call__(self, obj: Processor | Callable[..., Any] | None = None, default: P class Pipeline(Processor): """The abstract base class for all pipelines.""" - def checkpoint_processes(self) -> OrderedDict[str, Processor]: - """Finds the ``Processor`` that is a checkpoint and is closest to the output. The final checkpoint processor and all following processors are - retrieved and returned in an ``OrderedDict``. + def checkpoint_processes(self) -> collections.OrderedDict[str, Processor]: + """Finds the :py:class:`~corelay.processor.base.Processor` that is a checkpoint and is closest to the output. The final checkpoint + :py:class:`~corelay.processor.base.Processor` and all following instances of :py:class:`~corelay.processor.base.Processor` are retrieved and + returned in an :py:class:`collections.OrderedDict`. Raises: RuntimeError: No checkpoints were defined. Returns: - OrderedDict[str, Processor]: Returns an ``OrderedDict`` that contains the ``Processor`` that is closest to the output and a checkpoint, as - well as all following processors. The processors in the ``OrderedDict`` are ordered in the same way as they were in the pipeline, - i.e., from the checkpoint processor to the output processor. + collections.OrderedDict[str, Processor]: Returns an :py:class:`collections.OrderedDict` that contains the + :py:class:`~corelay.processor.base.Processor` that is closest to the output and a checkpoint, as well as all following instances of + :py:class:`~corelay.processor.base.Processor`. The instances of :py:class:`~corelay.processor.base.Processor` in th + :py:class:`collections.OrderedDict` are ordered in the same way as they were in the instance of :py:class:`Pipeline`, i.e., from the + checkpoint :py:class:`~corelay.processor.base.Processor` to the output :py:class:`~corelay.processor.base.Processor`. """ checkpoint_processor_list = [] @@ -248,18 +285,21 @@ def checkpoint_processes(self) -> OrderedDict[str, Processor]: if checkpoint_processor_list and not checkpoint_processor_list[-1][1].is_checkpoint: raise RuntimeError('No checkpoints were defined.') - checkpoint_processors = OrderedDict(checkpoint_processor_list[::-1]) + checkpoint_processors = collections.OrderedDict(checkpoint_processor_list[::-1]) return checkpoint_processors - def from_checkpoint(self) -> Any: - """Re-evaluates the pipeline from the last check-pointed ``Processor`` using the output from the checkpoint as input. + def from_checkpoint(self) -> typing.Any: + """Re-evaluates the :py:class:`Pipeline` from the last check-pointed :py:class:`~corelay.processor.base.Processor` using the output from the + checkpoint as input. Raises: - RuntimeError: If the check-pointed ``Processor`` closest to output does not have any ``checkpoint_data`` stored, i.e., the ``Processor`` - has not been called since being declared a checkpoint. + RuntimeError: If the check-pointed :py:class:`~corelay.processor.base.Processor` closest to output does not have any + :py:attr:`~corelay.processor.base.Processor.checkpoint_data` stored, i.e., the :py:class:`~corelay.processor.base.Processor` has not + been called since being declared a checkpoint. Returns: - Any: Returns the output of pipeline, starting from check-pointed ``Processor`` closest to output. + typing.Any: Returns the output of :py:class:`Pipeline`, starting from check-pointed :py:class:`~corelay.processor.base.Processor` closest + to output. """ checkpoint_processes = self.checkpoint_processes() @@ -271,16 +311,19 @@ def from_checkpoint(self) -> Any: data = processor(data) return data - def function(self, data: Any) -> Any: - """Propagate `data` through the whole pipeline from front to back, calling all Processors in series. + def function(self, data: typing.Any) -> typing.Any: + """Propagate `data` through the whole :py:class:`Pipeline` from front to back, calling all Processors in series. Args: - data (Any): The input data to the pipeline. This is the input data for the first ``Processor`` in the pipeline. The type of the input data - depends on the first ``Processor`` in the pipeline. + data (typing.Any): The input data to the :py:class:`Pipeline`. This is the input data for the first + :py:class:`~corelay.processor.base.Processor` in the :py:class:`Pipeline`. The type of the input data depends on the first + :py:class:`~corelay.processor.base.Processor` in the :py:class:`Pipeline`. Returns: - Any: Returns the output of the pipeline, which is the output of the of all processors in the pipeline that are flagged as pipeline - outputs. If no processors are flagged as outputs, the output of the last processor is returned. + typing.Any: Returns the output of the :py:class:`Pipeline`, which is the output of the of all instances of + :py:class:`~corelay.processor.base.Processor` in the :py:class:`Pipeline` that are flagged as outputs. If no instances of + :py:class:`~corelay.processor.base.Processor` are flagged as outputs, the output of the last :py:class:`~corelay.processor.base.Processor` + is returned. """ outputs = [] @@ -295,20 +338,22 @@ def function(self, data: Any) -> Any: return tuple(outputs) def __repr__(self) -> str: - """Generates a string representation of the pipeline, which contains all processors in the pipeline and their output types. + """Generates a :py:class:`str` representation of the :py:class:`Pipeline`, which contains all instances of + :py:class:`~corelay.processor.base.Processor` in the :py:class:`Pipeline` and their output types. Example: >>> MyPipeline() MyPipeline( - FunctionProcessor(processing_function=lambda x: x.mean(1),) -> output:numpy.ndarray - SciPyPDist(metric=sqeuclidean) -> output:numpy.ndarray - RadialBasisFunction(sigma=0.1) -> output:numpy.ndarray - MyProcess(stuff=3, func=Param(FunctionType, lambda x: x**2)) -> output:numpy.ndarray + FunctionProcessor(processing_function=lambda x: x.mean(1),) -> numpy.ndarray + SciPyPDist(metric=sqeuclidean) -> numpy.ndarray + RadialBasisFunction(sigma=0.1) -> numpy.ndarray + MyProcess(stuff=3, func=Param(FunctionType, lambda x: x**2)) -> numpy.ndarray ) Returns: - str: Returns a string representation of the pipeline, which contains all processors in the pipeline and their output types. + str: Returns a :py:class:`str` representation of the :py:class:`Pipeline`, which contains all instances of + :py:class:`~corelay.processor.base.Processor` in the :py:class:`Pipeline` and their output types. """ - pipeline = '\n '.join([processor.__repr__() for processor in self.collect_attr(Task).values()]) + pipeline = '\n '.join([repr(processor) for processor in self.collect_attr(Task).values()]) return f'{self.__class__.__name__}(\n {pipeline}\n)' diff --git a/source/corelay/pipeline/spectral.py b/source/corelay/pipeline/spectral.py index ad62843..828dd91 100644 --- a/source/corelay/pipeline/spectral.py +++ b/source/corelay/pipeline/spectral.py @@ -1,7 +1,14 @@ -"""A module that contains SprAy-specific spectral clustering pipelines.""" +"""A module that contains :py:class:`~corelay.pipeline.base.Pipeline` implementations for spectral embeddings, +:py:class:`~corelay.pipeline.spectral.SpectralEmbedding`, and spectral clustering, :py:class:`~corelay.pipeline.spectral.SpectralClustering`. These +are specific to `Spectral Relevance Analysis (SprAy) `_, an explainable artificial intelligence +(XAI) method for bridging the gap between local and global XAI. +""" + +import typing from corelay.pipeline.base import Pipeline, Task from corelay.processor.affinity import SparseKNN +from corelay.processor.base import Processor from corelay.processor.clustering import KMeans from corelay.processor.distance import SciPyPDist from corelay.processor.embedding import EigenDecomposition @@ -10,20 +17,20 @@ class SpectralEmbedding(Pipeline): """A pipeline for spectral embeddings, which is customizable with different pre-processing, pairwise distance, affinity, laplacian, and embedding - functions. When an instance of the pipeline is called, it will return eigenvalues and eigenvectors of the spectral embedding, as NumPy arrays. - + functions. When an instance of the pipeline is called, it will return eigenvalues and eigenvectors of the spectral embedding, as instances of + :py:class:`~numpy.ndarray`. Args: - preprocessing (Callable[[NDArray[Any]], Any], optional): A custom pre-processing function to be applied to the data before computing the - pairwise distance. Defaults to the identity function. - pairwise_distance (Callable[[NDArray[Any]], Any], optional): A custom pairwise distance function to be applied to the data. Defaults to the - euclidean distance. - affinity (Callable[[NDArray[Any]], Any], optional): A custom affinity function to be applied to the pairwise distance matrix. Defaults to a - sparse k-nearest neighbors graph with 10 neighbors. - laplacian (Callable[[NDArray[Any]], Any], optional): A custom graph laplacian function to be applied to the pairwise distance matrix. Defaults - to a symmetric normal laplacian. - embedding (Callable[[NDArray[Any]], Any], optional): A custom embedding function to be applied to the graph laplacian. Defaults to an - eigen decomposition with 32 eigenvalues. + preprocessing (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom pre-processing function to be applied to + the data before computing the pairwise distance. Defaults to the identity function. + pairwise_distance (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A pairwise distance function to be applied to + the data. Defaults to the euclidean distance. + affinity (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom affinity function to be applied to the pairwise + distance matrix. Defaults to a sparse k-nearest neighbors graph with 10 neighbors. + laplacian (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom graph laplacian function to be applied to the + affinity matrix. Defaults to a symmetric normal laplacian. + embedding (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom embedding function to be applied to the graph + laplacian. Defaults to an eigen-decomposition with 32 eigenvalues. Notes: Pre-computed distance matrices can be supplied by passing `pairwise_distance=lambda x: x`. @@ -31,19 +38,19 @@ class SpectralEmbedding(Pipeline): Pre-computed graph laplacian matrices can be supplied by further passing `laplacian=lambda x: x`. """ - preprocessing = Task(default=lambda x: x) + preprocessing: typing.Annotated[Processor, Task(default=lambda x: x)] """A pre-processing task to be applied to the data before computing the pairwise distance task. Defaults to the identity function.""" - pairwise_distance = Task(default=SciPyPDist(metric='euclidean')) + pairwise_distance: typing.Annotated[Processor, Task(default=SciPyPDist(metric='euclidean'))] """A pairwise distance task to be applied to the data. Defaults to the euclidean distance.""" - affinity = Task(default=SparseKNN(n_neighbors=10, symmetric=True)) + affinity: typing.Annotated[Processor, Task(default=SparseKNN(n_neighbors=10, symmetric=True))] """An affinity task to be applied to the pairwise distance matrix. Defaults to a sparse k-nearest neighbors graph with 10 neighbors.""" - laplacian = Task(default=SymmetricNormalLaplacian()) - """A graph laplacian task to be applied to the pairwise distance matrix. Defaults to a symmetric normal laplacian.""" + laplacian: typing.Annotated[Processor, Task(default=SymmetricNormalLaplacian())] + """A graph laplacian task to be applied to the affinity matrix. Defaults to a symmetric normal laplacian.""" - embedding = Task(default=EigenDecomposition(n_eigval=32), is_output=True) + embedding: typing.Annotated[Processor, Task(default=EigenDecomposition(n_eigval=32), is_output=True)] """An embedding task to be applied to the graph laplacian matrix. Defaults to an eigen decomposition with 32 eigenvalues.""" @@ -53,24 +60,24 @@ class SpectralClustering(SpectralEmbedding): arrays. Args: - select_eigenvector (Callable[[NDArray[Any]], Any], optional): A custom task to select the eigenvector from the spectral embedding. Defaults to - the second output of the spectral embedding. - clustering (Callable[[NDArray[Any]], Any], optional): A custom clustering function to be applied to the spectral embedding. Defaults to a - k-Means clustering with 2 clusters. - preprocessing (Callable[[NDArray[Any]], Any], optional): A custom pre-processing function to be applied to the data before computing the - pairwise distance. Defaults to the identity function. - pairwise_distance (Callable[[NDArray[Any]], Any], optional): A custom pairwise distance function to be applied to the data. Defaults to the - euclidean distance. - affinity (Callable[[NDArray[Any]], Any], optional): A custom affinity function to be applied to the pairwise distance matrix. Defaults to a - sparse k-nearest neighbors graph with 10 neighbors. - laplacian (Callable[[NDArray[Any]], Any], optional): A custom graph laplacian function to be applied to the pairwise distance matrix. Defaults - to a symmetric normal laplacian. - embedding (Callable[[NDArray[Any]], Any], optional): A custom embedding function to be applied to the graph laplacian. Defaults to an - eigen decomposition with 32 eigenvalues. + preprocessing (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom pre-processing function to be applied to + the data before computing the pairwise distance. Defaults to the identity function. + pairwise_distance (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom pairwise distance function to be + applied to the data. Defaults to the euclidean distance. + affinity (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom affinity function to be applied to the pairwise + distance matrix. Defaults to a sparse k-nearest neighbors graph with 10 neighbors. + laplacian (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom graph laplacian function to be applied to the + affinity matrix. Defaults to a symmetric normal laplacian. + embedding (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom embedding function to be applied to the graph + laplacian. Defaults to an eigen decomposition with 32 eigenvalues. + select_eigenvector (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom task to select the eigenvectors from + the output of the spectral embedding. Defaults to the second output of the spectral embedding. + clustering (Processor | Callable[[numpy.ndarray[typing.Any, typing.Any]], typing.Any]): A custom clustering function to be applied to the + spectral embedding. Defaults to a k-Means clustering with 2 clusters. """ - select_eigenvector = Task(default=lambda x: x[1]) + select_eigenvector: typing.Annotated[Processor, Task(default=lambda x: x[1])] """A task to select the eigenvector from the spectral embedding. Defaults to the second output of the spectral embedding.""" - clustering = Task(default=KMeans(n_clusters=2), is_output=True) + clustering: typing.Annotated[Processor, Task(default=KMeans(n_clusters=2), is_output=True)] """A clustering task to be applied to the spectral embedding. Defaults to a k-Means clustering with 2 clusters.""" diff --git a/source/corelay/plugboard.py b/source/corelay/plugboard.py index cdbc5f2..56bc323 100644 --- a/source/corelay/plugboard.py +++ b/source/corelay/plugboard.py @@ -1,29 +1,34 @@ -"""A module that contains plugboards, which are classes that contains slots filled using plugs.""" +"""A module that contains the :py:class:`~corelay.plugboard.Plugboard` class. Plugboards manage instances of :py:class:`~corelay.plugboard.Slot`, +which describe values. A :py:class:`~corelay.plugboard.Slot` can be filled using a :py:class:`~corelay.plugboard.Plug`, which represents a concrete +value. +""" +import typing from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, LambdaType, MethodType -from typing import Any import numpy from corelay.tracker import Tracker +from corelay.utils import get_fully_qualified_name class EmptyInit: - """Empty Init is a class intended to be inherited as a last step down the MRO, to catch any remaining positional and/or keyword arguments and thus - raise proper Exceptions. + """:py:class:`EmptyInit` is a class intended to be inherited as a last step down the MRO, to catch any remaining positional and/or keyword + arguments and thus raise proper exceptions. """ def __init__(self) -> None: - """Initializes a new instance of EmptyInit. + """Initializes a new instance of :py:class:`EmptyInit`. Note: This is not intended to be called directly, but rather by the constructor of the next class up in the inheritance hierarchy. The super-delegation is not unnecessary, even if PyLint claims so, since this is causes Python to raise a more informative exception when the user tries to pass more keyword arguments than are accepted by the constructors in the inheritance hierarchy. If the constructor of the penultimate class in inheritance hierarchy calls this constructor and there are still keyword arguments left, Python will raise a - TypeError with a message like: "TypeError: object.__init__() takes exactly one argument (the instance to initialize)". This may confuse - users, but if the constructor call is delegated to the next class up (i.e., object), the exception will be more informative and say - something like: "TypeError: Empty.__init__() takes 0 positional arguments but 1 was given". + :py:class:`TypeError` with a message like ":py:class:`TypeError`: `object.__init__()` takes exactly one argument (the instance to + initialize)". This may confuse users, but if the constructor call is delegated to the next class up (i.e., :py:class:`object`), the + exception will be more informative and say something like: ":py:class:`TypeError`: `Empty.__init__()` takes 0 positional arguments but 1 + was given". """ # pylint: disable=useless-super-delegation @@ -31,39 +36,40 @@ def __init__(self) -> None: class Slot(EmptyInit): - """Slots are descriptors that contain objects in a container called ``Plug``. Instances of the ``Slot`` class have a ``dtype`` and a ``default`` - value, which are enforced to be consistent. When a ``Slot`` instance is accessed in a class, it will return the contained object of its ``Plug`` - container. When accessing or assigning ``Slot`` instances in a class that have never been accessed before, a ``Plug`` object is stored in the - class' ``__dict__`` under the same name the ``Slot`` was assigned to in the class. Slots may have their default value set to `None`, in which case - setting Plugs belonging to it must have either a default value, or an explicit ``obj`` value on their own. Calling a ``Slot`` instance creates a - corresponding ``Plug`` container instance. + """Slots are descriptors that contain objects in a container called :py:class:`Plug`. Instances of the :py:class:`Slot` class have a + :py:attr:`~Slot.dtype` and a :py:attr:`~Slot.default` value, which are enforced to be consistent. When a :py:class:`Slot` instance is accessed in + a class, it will return the contained object of its :py:class:`Plug` container. When accessing or assigning :py:class:`Slot` instances in a class + that have never been accessed before, a :py:class:`Plug` object is stored in the class' ``__dict__`` under the same name the :py:class:`Slot` was + assigned to in the class. Slots may have their :py:attr:`~Slot.default` value set to :py:obj:`None`, in which case setting plugs belonging to it + must have either a :py:attr:`Plug.default` value, or an explicit :py:attr:`Plug.obj` value on their own. Calling a :py:class:`Slot` instance + creates a corresponding :py:class:`Plug` container instance. Note: See `https://docs.python.org/3/howto/descriptor.html` for more information on descriptors. See Also: - :obj:`Plugboard` - :obj:`Plug` + * :py:class:`Plugboard` + * :py:class:`Plug` """ def __init__( self, dtype: type | tuple[type, ...] = object, - default: Any = None, - **kwargs: Any + default: typing.Any = None, + **kwargs: typing.Any ) -> None: - """Initializes a new instance of ``Slot``. Configures that data type and the default value of the slot. A consistency check is performed to - ensure that the default value is of the correct type. + """Initializes a new :py:class:`Slot` instance. Configures that data type and the default value of the slot. A consistency check is performed + to ensure that the default value is of the correct type. Args: - dtype (type | tuple[type, ...], optional): The data type of the slot. This can be a single type or a tuple of types. The value of the - plug, as well as the default value, must be of this type or one of the types in the tuple. Defaults to ``object``. - default (Any, optional): The default value of the plug. The default value must be an instance of the specified data type ``dtype`` or one - of the types in the tuple. If no default value is set, it will be `None`. When a plug is created without an explicit value, it will - use this default value. Defaults to `None`. - **kwargs (Any): Additional keyword arguments that are passed to the parent class constructor. This is done for cooperativity's sake, as - the next class one step up in the inheritance hierarchy will be `EmptyInit`, which does not accept any additional keyword arguments - and will raise an exception if any are passed. + dtype (type | tuple[type, ...]): The data type of the slot. This can be a single type or a tuple of types. The value of the plug, as well + as the default value, must be of this type or one of the types in the tuple. Defaults to :py:class:`object`. + default (typing.Any): The default value of the plug. The default value must be an instance of the specified data type + :py:attr:`~Slot.dtype` or one of the types in the tuple. If no default value is set, it will be :py:obj:`None`. When a plug is created + without an explicit value, it will use this default value. Defaults to :py:obj:`None`. + **kwargs (typing.Any): Additional keyword arguments that are passed to the parent class constructor. This is done for cooperativity's + sake, as the next class one step up in the inheritance hierarchy will be `EmptyInit`, which does not accept any additional keyword + arguments and will raise an exception if any are passed. """ super().__init__(**kwargs) @@ -82,21 +88,22 @@ def __init__( numpy.ufunc, type(numpy.max) ) - """Contains all types that may represent a function. This is necessary, since many functions are not of type ``FunctionType``, e.g., lambda - expressions, methods, built-in functions, built-in methods, and NumPy universal functions. Also, since NumPy 1.26, NumPy array functions are no - longer actual functions; so, for example, something like `pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)]` would not - work anymore. When the user sets the dtype to ``FunctionType`` or ``FunctionType`` is an element of the tuple, then the types in this tuple are - added to the data types that ``_consistent`` checks against. This way, the user does not have to worry about the fact that many functions are not - of type ``FunctionType``. + """Contains all types that may represent a function. This is necessary, since many functions are not of type :py:class:`~types.FunctionType`, + e.g., lambda expressions, methods, built-in functions, built-in methods, and NumPy universal functions. Also, since NumPy 1.26, NumPy array + functions are no longer actual functions; so, for example, something like + `pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)]` would not work anymore. When the user sets the :py:attr:`~Slot.dtype` + to :py:class:`~types.FunctionType` or :py:class:`~types.FunctionType` is an element of the tuple, then the types in this tuple are added to the + data types that the consistency check checks against. This way, the user does not have to worry about the fact that many functions are not of type + :py:class:`~types.FunctionType`. """ def _consistent(self) -> None: - """Checks whether ``dtype`` and ``default`` are consistent, i.e., ``default`` is either `None`, of type ``dtype``, or one of the types in the - tuple ``dtype``. + """Checks whether :py:attr:`~Slot.dtype` and :py:attr:`~Slot.default` are consistent, i.e., :py:attr:`~Slot.default` is either :py:obj:`None`, + of type :py:attr:`~Slot.dtype`, or one of the types in the tuple :py:attr:`~Slot.dtype`. Raises: - TypeError: The ``default`` value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The :py:attr:`~Slot.default` value is not consistent with the :py:attr:`~Slot.dtype`, i.e., it is neither :py:obj:`None`, nor + of the type :py:attr:`~Slot.dtype` or one of the types in the tuple :py:attr:`~Slot.dtype`. """ if ( @@ -121,19 +128,19 @@ def _consistent(self) -> None: f'The data type of the default value of the "{type(self).__name__}" object "{self.__name__}" is not of type "{self.dtype}".' ) - def get_plug(self, instance: Any, obj: Any = None, default: Any = None) -> 'Plug': - """Gets a corresponding ``Plug`` that can be used to access the ``__dict__`` of the ``Slot`` instance . In case a new ``Plug`` has to be - created, a ``obj`` and ``default`` value may be specified. + def get_plug(self, instance: typing.Any, obj: typing.Any = None, default: typing.Any = None) -> 'Plug': + """Gets a corresponding :py:class:`Plug` that can be used to access the ``__dict__`` of the :py:class:`Slot` instance . In case a new + :py:class:`Plug` has to be created, the ``obj`` and ``default`` parameters may be specified. Args: - instance (Any): An instance of the class the ``Slot`` was assigned in. - obj (Any): The object value to write to newly created ``Plug`` instances. - default (Any): The default value to write to newly created ``Plug`` instances. + instance (typing.Any): An instance of the class the :py:class:`Slot` was assigned in. + obj (typing.Any): The object value to write to newly created :py:class:`Plug` instances. + default (typing.Any): The default value to write to newly created :py:class:`Plug` instances. Returns: - Plug: Returns a ``Plug`` container. If a ``Plug`` instance already exists in the instance's ``__dict__`` it is returned. Otherwise, a new - ``Plug`` container is created, which is also appended to the instance's ``__dict__``. If a new ``Plug`` instance is created, the ``obj`` - and ``default`` values are set to the values passed in. + Plug: Returns a :py:class:`Plug` container. If a :py:class:`Plug` instance already exists in the instance's ``__dict__`` it is returned. + Otherwise, a new :py:class:`Plug` container is created, which is also appended to the instance's ``__dict__``. If a new :py:class:`Plug` + instance is created, the :py:attr:`~Plug.obj` and :py:attr:`~Plug.default` values are set to the values passed in. """ try: @@ -144,71 +151,85 @@ def get_plug(self, instance: Any, obj: Any = None, default: Any = None) -> 'Plug return plug def __set_name__(self, owner: type, name: str) -> None: # pylint: disable=unused-argument - """Is invoked, when the ``Slot`` is assigned to a class or instance attribute. Sets the name of the slot when assigned under a class. + """Is invoked, when the :py:class:`Slot` is assigned to a class or instance attribute. Sets the name of the slot when assigned under a class. Necessary to write the correct ``__dict__`` entry in the parent class. Args: - owner (type): The parent class the ``Slot`` was assigned in. - name (str): The name under which the ``Slot`` was assigned in the parent class. + owner (type): The parent class the :py:class:`Slot` was assigned in. + name (str): The name under which the :py:class:`Slot` was assigned in the parent class. """ self.__name__ = name - def __get__(self, instance: Any, owner: type) -> 'Slot | Any': # pylint: disable=unused-argument - """Is invoked, when the class or instance attribute the ``Slot`` was assigned to is read. When the ``Slot`` is accessed from a class, the - ``Slot`` instance itself is returned. If accessed using an instance, the corresponding ``Plug`` container's value is returned. + def __get__(self, instance: typing.Any, owner: type) -> 'Slot | typing.Any': # pylint: disable=unused-argument + """Is invoked, when the class or instance attribute the :py:class:`Slot` was assigned to is read. When the :py:class:`Slot` is accessed from a + class, the :py:class:`Slot` instance itself is returned. If accessed using an instance, the corresponding :py:class:`Plug` container's value + is returned. Args: - instance (Any): The instance of the parent class the ``Slot`` was assigned in. - owner (type): The parent class the ``Slot`` was defined in. + instance (typing.Any): The instance of the parent class the :py:class:`Slot` was assigned in. + owner (type): The parent class the :py:class:`Slot` was defined in. Returns: - Slot | Any: Returns the value of the Plug container. + Slot | typing.Any: Returns the value of the Plug container. """ return self if instance is None else self.get_plug(instance).obj - def __set__(self, instance: Any, value: Any) -> None: - """Is invoked, when the class or instance attribute the ``Slot`` was assigned to is written. Sets the instance's ``Plug`` container object - value. + def __set__(self, instance: typing.Any, value: typing.Any) -> None: + """Is invoked, when the class or instance attribute the :py:class:`Slot` was assigned to is written. Sets the instance's :py:class:`Plug` + container object value. Args: - instance (Any): The instance of the parent class the ``Slot`` was assigned in. - value (Any): The value to set the Plug container's object value to. + instance (typing.Any): The instance of the parent class the :py:class:`Slot` was assigned in. + value (typing.Any): The value to set the Plug container's object value to. """ self.get_plug(instance, obj=value).obj = value - def __delete__(self, instance: Any) -> None: - """Is invoked, when the class or instance attribute the ``Slot`` was assigned to is deleted. Deletes the instance's ``Plug`` container object - value if it exists, enforcing the use of its default value. + def __delete__(self, instance: typing.Any) -> None: + """Is invoked, when the class or instance attribute the :py:class:`Slot` was assigned to is deleted. Deletes the instance's :py:class:`Plug` + container object value if it exists, enforcing the use of its default value. Args: - instance (Any): The instance of the parent class the ``Slot`` was assigned in. + instance (typing.Any): The instance of the parent class the :py:class:`Slot` was assigned in. """ del self.get_plug(instance).obj + def __repr__(self) -> str: + """Returns a :py:class:`str` representation of the :py:class:`Slot` instance. + + Returns: + str: Returns a :py:class:`str` representation of the :py:class:`Slot` instance. + """ + + # Sphinx AutoDoc uses __repr__ when it encounters the metadata of typing.Annotated; this is a reasonable thing to do, but then it tries to + # resolve the resulting string as types for cross-referencing, which is not possible with the default implementation of __repr__; to be able + # to get proper documentation, the fully-qualified name of the class is returned, because this enable Sphinx AutoDoc to reference the class in + # the documentation + return f'~{get_fully_qualified_name(self)}' + @property - def default(self) -> Any: - """Gets or sets the default value of the ``Slot``. + def default(self) -> typing.Any: + """Gets or sets the default value of the :py:class:`Slot`. Returns: - Any: Returns the slot's default value. If not set, `None` is returned. + typing.Any: Returns the slot's default value. If not set, :py:obj:`None` is returned. """ return self._default @default.setter - def default(self, value: Any) -> None: - """Gets or sets the default value of the ``Slot``. Checks the new default value for consistency with the ``dtype``. + def default(self, value: typing.Any) -> None: + """Gets or sets the default value of the :py:class:`Slot`. Checks the new default value for consistency with the :py:attr:`~Slot.dtype`. Args: - value (Any): The new default value to set. If not set, `None` is returned. + value (typing.Any): The new default value to set. If not set, :py:obj:`None` is returned. Raises: - TypeError: The default value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The default value is not consistent with the :py:attr:`~Slot.dtype`, i.e., it is neither :py:obj:`None`, nor of the type + :py:attr:`~Slot.dtype` or one of the types in the tuple :py:attr:`~Slot.dtype`. """ original_default_value = self._default @@ -224,30 +245,30 @@ def default(self, value: Any) -> None: @default.deleter def default(self) -> None: - """Deletes the default value of the ``Slot``.""" + """Deletes the default value of the :py:class:`Slot`.""" self._default = None @property def dtype(self) -> type | tuple[type, ...]: - """Gets or sets the slot's ``dtype``. + """Gets or sets the slot's :py:attr:`~Slot.dtype`. Returns: - type | tuple[type, ...]: Returns the slot's ``dtype``. If not set, `None` is returned. + type | tuple[type, ...]: Returns the slot's :py:attr:`~Slot.dtype`. If not set, :py:obj:`None` is returned. """ return self._dtype @dtype.setter def dtype(self, value: type | tuple[type, ...]) -> None: - """Gets or sets the slot's ``dtype``. Checks the default value of the slot for consistency with the new ``dtype``. + """Gets or sets the slot's :py:attr:`~Slot.dtype`. Checks the default value of the slot for consistency with the new :py:attr:`~Slot.dtype`. Args: - value (type | tuple[type, ...]): The new ``dtype`` to set. If not set, `None` is returned. + value (type | tuple[type, ...]): The new :py:attr:`~Slot.dtype` to set. If not set, :py:obj:`None` is returned. Raises: - TypeError: The default value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The default value is not consistent with the :py:attr:`~Slot.dtype`, i.e., it is neither :py:obj:`None`, nor of the type + :py:attr:`~Slot.dtype` or one of the types in the tuple :py:attr:`~Slot.dtype`. """ original_dtype_value = self._dtype @@ -263,46 +284,48 @@ def dtype(self, value: type | tuple[type, ...]) -> None: @property def optional(self) -> bool: - """Gets a value indicating whether the ``Slot`` is optional. + """Gets a value indicating whether the :py:class:`Slot` is optional. Returns: - bool: Returns `True` if the ``Slot`` is optional, i.e., it has a default value, and `False` otherwise. + bool: Returns :py:obj:`True` if the :py:class:`Slot` is optional, i.e., it has a default value, and :py:obj:`False` otherwise. """ return self.default is not None - def __call__(self, obj: Any = None, default: Any = None) -> 'Plug': + def __call__(self, obj: typing.Any = None, default: typing.Any = None) -> 'Plug': """Create a new corresponding Plug container Args: - obj (Any): A value to initialize the newly created ``Plug`` container's object value to. Defaults to `None`. - default (Any): A value to initialize the newly created ``Plug`` container's default value to. Defaults to `None`. + obj (typing.Any): A value to initialize the newly created :py:class:`Plug` container's object value to. Defaults to :py:obj:`None`. + default (typing.Any): A value to initialize the newly created :py:class:`Plug` container's default value to. Defaults to :py:obj:`None`. Returns: - Plug: Returns a newly created ``Plug`` container instance, obeying the type and optionality constraints. + Plug: Returns a newly created :py:class:`Plug` container instance, obeying the type and optionality constraints. """ return Plug(self, obj=obj, default=default) class Plug(EmptyInit): - """Container class to fill slots associated with a certain instance. The instance is usually of type ``Plugboard``, but may be of any kind of - type. + """Container class to fill slots associated with a certain instance. The instance is usually of type :py:class:`Plugboard`, but may be of any kind + of type. See Also: - :obj:`Slot` - :obj:`Plugboard` + * :py:class:`Slot` + * :py:class:`Plugboard` """ - def __init__(self, slot: Slot, obj: Any = None, default: Any = None, **kwargs: Any) -> None: - """Initializes a new ``Plug`` instance and checks for consistency. + def __init__(self, slot: Slot, obj: typing.Any = None, default: typing.Any = None, **kwargs: typing.Any) -> None: + """Initializes a new :py:class:`Plug` instance and checks for consistency. Args: - slot (Slot): The ``Slot`` instance to associate with this ``Plug``. - obj (Any): An explicitly defined object held in the ``Plug`` container. If not set, ``default`` is returned as its value. - default (Any): A plug-dependent lower-priority object held in the ``Plug`` container. If not set, ``fallback`` is returned. - **kwargs (Any): Keyword arguments passed down to the base class constructor, for cooperativity's sake. In normal cases, this next class - will be `EmptyInit`, which accepts no more keyword arguments and will raise an exception. + slot (Slot): The :py:class:`Slot` instance to associate with this :py:class:`Plug`. + obj (typing.Any): An explicitly defined object held in the :py:class:`Plug` container. If not set, :py:attr:`~Plug.default` is returned as + its value. + default (typing.Any): A plug-dependent lower-priority object held in the :py:class:`Plug` container. If not set, :py:attr:`~Plug.fallback` + is returned. + **kwargs (typing.Any): Keyword arguments passed down to the base class constructor, for cooperativity's sake. In normal cases, this next + class will be :py:class:`EmptyInit`, which accepts no more keyword arguments and will raise an exception. """ super().__init__(**kwargs) @@ -321,21 +344,22 @@ def __init__(self, slot: Slot, obj: Any = None, default: Any = None, **kwargs: A numpy.ufunc, type(numpy.max) ) - """Contains all types that may represent a function. This is necessary, since many functions are not of type ``FunctionType``, e.g., lambda - expressions, methods, built-in functions, built-in methods, and NumPy universal functions. Also, since NumPy 1.26, NumPy array functions are no - longer actual functions; so, for example, something like `pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)]` would not - work anymore. When the user sets the dtype to ``FunctionType`` or ``FunctionType`` is an element of the tuple, then the types in this tuple are - added to the data types that ``_consistent`` checks against. This way, the user does not have to worry about the fact that many functions are not - of type ``FunctionType``. + """Contains all types that may represent a function. This is necessary, since many functions are not of type :py:class:`~types.FunctionType`, + e.g., lambda expressions, methods, built-in functions, built-in methods, and NumPy universal functions. Also, since NumPy 1.26, NumPy array + functions are no longer actual functions; so, for example, something like + `pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)]` would not work anymore. When the user sets the :py:attr:`~Slot.dtype` + to :py:class:`~types.FunctionType` or :py:class:`~types.FunctionType` is an element of the tuple, then the types in this tuple are added to the + data types that the consistency check checks against. This way, the user does not have to worry about the fact that many functions are not of type + :py:class:`~types.FunctionType`. """ def _consistent(self) -> None: - """Checks whether all values are consistent, i.e., at least one of ``obj``, ``default``, or ``fallback`` is set and of the data type specified - in `slot.dtype`, or one of the types in the tuple `slot.dtype`. + """Checks whether all values are consistent, i.e., at least one of :py:attr:`~Plug.obj`, :py:attr:`~Plug.default`, or + :py:attr:`~Plug.fallback` is set and of the data type specified in `slot.dtype`, or one of the types in the tuple `slot.dtype`. Raises: - TypeError: None of ``obj``, ``default``, or ``fallback`` is set, or the value is not consistent with the `slot.dtype` or one of the types - in the tuple `slot.dtype`. + TypeError: None of :py:attr:`~Plug.obj`, :py:attr:`~Plug.default`, or :py:attr:`~Plug.fallback` is set, or the value is not consistent + with the `slot.dtype` or one of the types in the tuple `slot.dtype`. """ if self.obj is None: @@ -352,20 +376,20 @@ def _consistent(self) -> None: @property def slot(self) -> Slot: - """Gets or sets the associated ``Slot``. + """Gets or sets the associated :py:class:`Slot`. Returns: - Slot: Returns the associated ``Slot``. If not set, `None` is returned. + Slot: Returns the associated :py:class:`Slot`. If not set, :py:obj:`None` is returned. """ return self._slot @slot.setter def slot(self, value: Slot) -> None: - """Gets or sets associated ``Slot`` and checks for consistency. + """Gets or sets associated :py:class:`Slot` and checks for consistency. Args: - value (Slot): The new ``Slot`` to set. + value (Slot): The new :py:class:`Slot` to set. """ self._slot = value @@ -373,41 +397,41 @@ def slot(self, value: Slot) -> None: @property def dtype(self) -> type | tuple[type, ...]: - """Gets the ``dtype`` of the associated ``Slot``. The ``dtype`` property is non-mutable. + """Gets the :py:attr:`Slot.dtype` of the associated :py:class:`Slot`. The :py:attr:`~Plug.dtype` property is non-mutable. Returns: - type | tuple[type, ...]: Returns the ``dtype`` of the associated ``Slot``. + type | tuple[type, ...]: Returns the :py:attr:`Slot.dtype` of the associated :py:class:`Slot`. """ return self.slot.dtype[0] if isinstance(self.slot.dtype, tuple) and len(self.slot.dtype) == 1 else self.slot.dtype @property def optional(self) -> bool: - """Gets a value indicating whether the ``Plug`` container has a default value. The ``optional`` property is non-mutable. + """Gets a value indicating whether the :py:class:`Plug` container has a default value. The :py:attr:`Plug.optional` property is non-mutable. Returns: - bool: Returns `True` if the ``Plug`` container has a default value, and `False` otherwise. + bool: Returns :py:obj:`True` if the :py:class:`Plug` container has a default value, and :py:obj:`False` otherwise. """ return self.default is not None @property - def fallback(self) -> Any: - """Gets the default value of the associated ``Slot``. The ``fallback`` property is non-mutable. + def fallback(self) -> typing.Any: + """Gets the default value of the associated :py:class:`Slot`. The :py:attr:`~Plug.fallback` property is non-mutable. Returns: - Any: Returns the default value of the associated ``Slot``. + typing.Any: Returns the default value of the associated :py:class:`Slot`. """ return self.slot.default @property - def obj(self) -> Any: - """Gets or sets the value of the object contained in the ``Plug``. If the ``Plug`` does not contain an object value, ``default`` is retrieved - instead. + def obj(self) -> typing.Any: + """Gets or sets the value of the object contained in the :py:class:`Plug`. If the :py:class:`Plug` does not contain an object value, + :py:attr:`~Plug.default` is retrieved instead. Returns: - Any: Returns the object value contained in the ``Plug``. If not set, ``default`` is returned. + typing.Any: Returns the object value contained in the :py:class:`Plug`. If not set, :py:attr:`~Plug.default` is returned. """ if self._obj is None: @@ -415,15 +439,15 @@ def obj(self) -> Any: return self._obj @obj.setter - def obj(self, value: Any) -> None: - """Gets or sets the value of the object contained in the ``Plug`` and checks for consistency. + def obj(self, value: typing.Any) -> None: + """Gets or sets the value of the object contained in the :py:class:`Plug` and checks for consistency. Args: - value (Any): The new object value to set. + value (typing.Any): The new object value to set. Raises: - TypeError: The object value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The object value is not consistent with the :py:attr:`~Plug.dtype`, i.e., it is neither :py:obj:`None`, nor of the type + :py:attr:`~Plug.dtype` or one of the types in the tuple :py:attr:`~Plug.dtype`. """ original_obj_value = self._obj @@ -437,16 +461,17 @@ def obj(self, value: Any) -> None: @obj.deleter def obj(self) -> None: - """Deletes the value of the object contained in the ``Plug`` by setting it to `None`.""" + """Deletes the value of the object contained in the :py:class:`Plug` by setting it to :py:obj:`None`.""" self.obj = None @property - def default(self) -> Any: - """Gets or sets the default value of the ``Plug``. If the ``default`` value is not set, then the ``fallback`` value is retrieved instead. + def default(self) -> typing.Any: + """Gets or sets the default value of the :py:class:`Plug`. If the :py:attr:`~Plug.default` value is not set, then the + :py:attr:`~Plug.fallback` value is retrieved instead. Returns: - Any: Returns the default value of the ``Plug``. If not set, ``fallback`` is returned. + typing.Any: Returns the default value of the :py:class:`Plug`. If not set, :py:attr:`~Plug.fallback` is returned. """ if self._default is None: @@ -454,15 +479,15 @@ def default(self) -> Any: return self._default @default.setter - def default(self, value: Any) -> None: - """Gets or sets the default value of the ``Plug`` and checks for consistency. + def default(self, value: typing.Any) -> None: + """Gets or sets the default value of the :py:class:`Plug` and checks for consistency. Args: - value (Any): The new default value to set. + value (typing.Any): The new default value to set. Raises: - TypeError: The default value is not consistent with the ``dtype``, i.e., it is neither `None`, nor of the type ``dtype`` or one of the - types in the tuple ``dtype``. + TypeError: The default value is not consistent with the :py:attr:`~Plug.dtype`, i.e., it is neither :py:obj:`None`, nor of the type + :py:attr:`~Plug.dtype` or one of the types in the tuple :py:attr:`~Plug.dtype`. """ original_default_value = self._default @@ -476,46 +501,47 @@ def default(self, value: Any) -> None: @default.deleter def default(self) -> None: - """Deletes the default value of the ``Plug`` by setting it to `None`.""" + """Deletes the default value of the :py:class:`Plug` by setting it to :py:obj:`None`.""" self.default = None class SlotDefaultAccess: - """A proxy-object descriptor class to access the default values of the owning class of a ``Slot``, since ``Slot`` instances cannot be returned - except by accessing a classes' ``__dict__``. + """A proxy-object descriptor class to access the default values of the owning class of a :py:class:`Slot`, since :py:class:`Slot` instances cannot + be returned except by accessing a classes' ``__dict__``. See Also: - :obj:`Slot` - :obj:`Plugboard` - :obj:`Plug` + * :py:class:`Slot` + * :py:class:`Plugboard` + * :py:class:`Plug` """ - def __init__(self, instance: Tracker | Any = None) -> None: - """Initializes a new ``SlotDefaultAccess`` instance. + def __init__(self, instance: Tracker | typing.Any = None) -> None: + """Initializes a new :py:class:`SlotDefaultAccess` instance. Args: - instance (Tracker | Any): The instance of the class the ``SlotDefaultAccess`` is associated with. + instance (Tracker | typing.Any): The instance of the class the :py:class:`SlotDefaultAccess` is associated with. """ # We cannot just assign self._instance here, because this would cause __get__ to be called, which in turn would create a new instance of # SlotDefaultAccess, which would call __init__ again, which would call __get__ again, and so on, resulting in an infinite recursion; this is # circumvented by using object.__setattr__ instead, which does not call __get__ - self._instance: Tracker | Any + self._instance: Tracker | typing.Any object.__setattr__(self, '_instance', instance) - def _get_plug(self, name: str, default: Any = None) -> Plug: - """Gets the ``Plug`` of the instance of the associated ``Slot``-owning class by name, by calling the ``get_plug`` method of the ``Slot``. + def _get_plug(self, name: str, default: typing.Any = None) -> Plug: + """Gets the :py:class:`Plug` of the instance of the associated :py:class:`Slot`-owning class by name, by calling the :py:meth:`~Slot.get_plug` + method of the :py:class:`Slot`. Args: - name (str): The name of the ``Slot``. - default (Any): The default value to set if the ``Plug`` associated with the ``Slot`` does not exist yet. + name (str): The name of the :py:class:`Slot`. + default (typing.Any): The default value to set if the :py:class:`Plug` associated with the :py:class:`Slot` does not exist yet. Raises: - AttributeError: There is no attribute in the associated owner class of this name of type ``Slot``. + AttributeError: There is no attribute in the associated owner class of this name of type :py:class:`Slot`. Returns: - Plug: Returns the ``Plug`` container associated with the instance of the ``Slot``-owning class and name. + Plug: Returns the :py:class:`Plug` container associated with the instance of the :py:class:`Slot`-owning class and name. """ if self._instance is None: @@ -525,29 +551,29 @@ def _get_plug(self, name: str, default: Any = None) -> Plug: raise AttributeError(f'"{type(self._instance)}" object has no attribute "{name}" of type "{Slot}", it is of type "{type(slot)}".') return slot.get_plug(self._instance, default=default) - def __get__(self, instance: Any, owner: Any) -> 'SlotDefaultAccess': # pylint: disable=unused-argument - """Gets a new instance of ``SlotDefaultAccess``, initialized with the provided instance value. + def __get__(self, instance: typing.Any, owner: typing.Any) -> 'SlotDefaultAccess': # pylint: disable=unused-argument + """Gets a new instance of :py:class:`SlotDefaultAccess`, initialized with the provided instance value. Args: - instance (Any): The instance of the class the ``SlotDefaultAccess`` is associated with. - owner (Any): The owner class of the ``SlotDefaultAccess``. + instance (typing.Any): The instance of the class the :py:class:`SlotDefaultAccess` is associated with. + owner (typing.Any): The owner class of the :py:class:`SlotDefaultAccess`. Returns: - SlotDefaultAccess: Returns a new instance of ``SlotDefaultAccess`` initialized with the provided instance value. + SlotDefaultAccess: Returns a new instance of :py:class:`SlotDefaultAccess` initialized with the provided instance value. """ return type(self)(instance) - def __set__(self, instance: Any, value: dict[str, Any]) -> None: - """Sets the default values of the associated owner class instance's slots by assigning a the values of the dictionary specified - in ``value``. + def __set__(self, instance: typing.Any, value: dict[str, typing.Any]) -> None: + """Sets the default values of the associated owner class instance's slots by assigning a the values of the :py:class:`dict` specified in + ``value``. Args: - instance (Any): The instance of the class the ``SlotDefaultAccess`` is associated with. - value (dict[str, Any]): A dictionary containing the default values to set for the associated owner class instance's slots. + instance (typing.Any): The instance of the class the :py:class:`SlotDefaultAccess` is associated with. + value (dict[str, typing.Any]): A :py:class:`dict` containing the default values to set for the associated owner class instance's slots. Raises: - TypeError: The ``value`` is not a dictionary. + TypeError: The ``value`` is not a :py:class:`dict`. """ if not isinstance(value, dict): @@ -557,7 +583,7 @@ def __set__(self, instance: Any, value: dict[str, Any]) -> None: for attribute_name, attribute_default_value in value.items(): setattr(slot_default_accessor, attribute_name, attribute_default_value) - def __getattr__(self, name: str) -> Any: + def __getattr__(self, name: str) -> typing.Any: """Gets the default value of the of the slot with the specified ``name`` that associated with the owner class instance. Args: @@ -567,7 +593,7 @@ def __getattr__(self, name: str) -> Any: AttributeError: There is no slot with the specified ``name`` in the associated owner class instance. Returns: - Any: Returns the default value of the slot with the specified ``name`` that associated with the owner class instance. + typing.Any: Returns the default value of the slot with the specified ``name`` that associated with the owner class instance. """ try: @@ -575,17 +601,17 @@ def __getattr__(self, name: str) -> Any: except AttributeError as exception: raise AttributeError(f'"{type(self._instance)}" object has no attribute "{name}" of type "{Slot}".') from exception - def __setattr__(self, name: str, value: Any) -> None: + def __setattr__(self, name: str, value: typing.Any) -> None: """Sets the default value of the slot with the specified ``name`` that associated with the owner class instance. Args: name (str): The name of the slot to set the default value for. - value (Any): The new default value to set. + value (typing.Any): The new default value to set. Raises: AttributeError: There is no slot with the specified ``name`` in the associated owner class instance. - TypeError: The default value is not consistent with the ``dtype`` of the ``Plug``, i.e., it is neither `None`, nor of the type ``dtype`` - or one of the types in the tuple ``dtype``. + TypeError: The default value is not consistent with the :py:attr:`~Plug.dtype` of the :py:class:`Plug`, i.e., it is neither + :py:obj:`None`, nor of the type :py:attr:`~Plug.dtype` or one of the types in the tuple :py:attr:`~Plug.dtype`. """ try: @@ -626,25 +652,25 @@ def __dir__(self) -> list[str]: class Plugboard(Tracker, EmptyInit): - """Optional Manager class for slots. Uses ``SlotDefaultAccess`` to access ``Plug`` default values. Also initializes ``Plug`` container object - values during instantiation by keywords. + """Optional Manager class for slots. Uses :py:class:`SlotDefaultAccess` to access :py:class:`Plug` default values. Also initializes + :py:class:`Plug` container object values during instantiation by keywords. See Also: - :obj:`Slot` - :obj:`SlotDefaultAccess` - :obj:`Plug` + * :py:class:`Slot` + * :py:class:`SlotDefaultAccess` + * :py:class:`Plug` """ default = SlotDefaultAccess() - """Contains a proxy object to access the default values of the owning class of a ``Plug``.""" + """Contains a proxy object to access the default values of the owning class of a :py:class:`Plug`.""" - def __init__(self, **kwargs: Any) -> None: - """Initializes a new ``Plugboard`` instance and initializes the slots via the keyword arguments passed in. + def __init__(self, **kwargs: typing.Any) -> None: + """Initializes a new :py:class:`Plugboard` instance and initializes the slots via the keyword arguments passed in. Args: - **kwargs (Any): The keyword arguments that are used to initialize slots. Only keyword arguments which correspond to the slot attribute - names of the class are processed. All other keyword arguments are passed to the constructor of the next class in the inheritance - hierarchy. + **kwargs (typing.Any): The keyword arguments that are used to initialize slots. Only keyword arguments which correspond to the slot + attribute names of the class are processed. All other keyword arguments are passed to the constructor of the next class in the + inheritance hierarchy. """ slots = self.collect(Slot) @@ -663,11 +689,11 @@ def reset_defaults(self) -> None: for attribute_name in self.collect(Slot): delattr(self.default, attribute_name) - def update_defaults(self, **kwargs: Any) -> None: + def update_defaults(self, **kwargs: typing.Any) -> None: """Updates the default values of all plugs of this instance using the keyword arguments. Args: - **kwargs (Any): The keyword arguments that are used to update the default values of the plugs. + **kwargs (typing.Any): The keyword arguments that are used to update the default values of the plugs. """ for attribute_name, new_attribute_default_value in kwargs.items(): diff --git a/source/corelay/processor/__init__.py b/source/corelay/processor/__init__.py index a045e0c..d15d2ef 100644 --- a/source/corelay/processor/__init__.py +++ b/source/corelay/processor/__init__.py @@ -1 +1,4 @@ -"""A sub-package containing base and built-in processors.""" +"""A sub-package that contains the abstract base class for all processors, :py:class:`~corelay.processor.base.Processor`. Processors perform a +specific operation in a :py:class:`~corelay.pipeline.base.Pipeline` that can be parameterized using instances of :py:class:`~corelay.base.Param`. +Furthermore, the sub-package contains a number of built-in processors for common operations. +""" diff --git a/source/corelay/processor/affinity.py b/source/corelay/processor/affinity.py index 14307ff..ab0d068 100644 --- a/source/corelay/processor/affinity.py +++ b/source/corelay/processor/affinity.py @@ -1,6 +1,7 @@ -"""Affinity (similarity) processors.""" +"""A module that contains processors for computing affinity, i.e., similarity, matrices for sets of measurements.""" -from typing import Annotated, Any +import typing +from typing import Annotated import numpy import scipy.sparse @@ -13,14 +14,17 @@ class Affinity(Processor): """The abstract base class for processors that compute affinity (i.e., similarity) matrices. Note: - Each sub-class has to implement a ``__call__`` method to compute its corresponding affinity matrix of some data. + Each sub-class has to implement a :py:meth:`Processor.__call__ ` method to compute its + corresponding affinity matrix of some data. Args: - is_output (bool, optional): A value indicating whether this ``Affinity`` processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`Affinity` processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. """ @@ -28,31 +32,32 @@ class SparseKNN(Affinity): """A processor for computing an affinity matrix using the sparse k-nearest neighbors (KNN) method. Args: - is_output (bool, optional): A value indicating whether this ``SparseKNN`` affinity processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - n_neighbors (int, optional): Number of neighbors to consider. Defaults to 10. - symmetric (bool, optional): If `True`, the affinity matrix is set to the mean of itself and its transpose. Defaults to `True`. + is_output (bool): A value indicating whether this :py:class:`SparseKNN` affinity processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + n_neighbors (int): Number of neighbors to consider. Defaults to 10. + symmetric (bool): If :py:obj:`True`, the affinity matrix is set to the mean of itself and its transpose. Defaults to :py:obj:`True`. """ n_neighbors: Annotated[int, Param(int, 10, identifier=True)] """A parameter for the number of neighbors to consider. Defaults to 10.""" symmetric: Annotated[bool, Param(bool, True, identifier=True)] - """A parameter for whether to make the affinity matrix symmetric. Defaults to `True`.""" + """A parameter for whether to make the affinity matrix symmetric. Defaults to :py:obj:`True`.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Compute Sparse K-Nearest-Neighbors affinity matrix. Args: - data (Any): A NumPy array ``numpy.ndarray`` containing the pairwise distances between samples, which is used to compute the affinity - matrix. + data (typing.Any): A NumPy array :py:class:`~numpy.ndarray` containing the pairwise distances between samples, which is used to compute + the affinity matrix. Returns: - Any: Returns a sparse CSR representation ``scipy.sparse.csr_matrix`` of the KNN affinity matrix. + typing.Any: Returns a sparse CSR representation :py:class:`~scipy.sparse.csr_matrix` of the KNN affinity matrix. """ number_of_neighbors = self.n_neighbors @@ -80,27 +85,28 @@ class RadialBasisFunction(Affinity): """A processor for computing an affinity matrix using the Radial Basis Function (RBF) kernel. Args: - is_output (bool, optional): A value indicating whether this ``RadialBasisFunction`` affinity processor is the output of a ``Pipeline``. - Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - sigma (float, optional): The scale of the RBF kernel. Defaults to 1.0. + is_output (bool): A value indicating whether this :py:class:`RadialBasisFunction` affinity processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + sigma (float): The scale of the RBF kernel. Defaults to 1.0. """ sigma: Annotated[float, Param(float, 1.0, identifier=True)] """A parameter for the scale of the RBF kernel. Defaults to 1.0.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Compute Radial Basis Function affinity matrix. Args: - data (Any): A NumPy array ``numpy.ndarray`` containing the pairwise distances between samples, which is used to compute the affinity - matrix. The data is expected to be a square matrix of shape (number_of_samples, number_of_samples). + data (typing.Any): A NumPy array :py:class:`~numpy.ndarray` containing the pairwise distances between samples, which is used to compute + the affinity matrix. The data is expected to be a square matrix of shape (number_of_samples, number_of_samples). Returns: - Any: Returns a NumPy array ``numpy.ndarray`` containing the RBF affinity matrix. + typing.Any: Returns a NumPy array :py:class:`~numpy.ndarray` containing the RBF affinity matrix. """ affinity = numpy.exp(-data / (2 * self.sigma ** 2)) diff --git a/source/corelay/processor/base.py b/source/corelay/processor/base.py index d26a9cd..ca7fe36 100644 --- a/source/corelay/processor/base.py +++ b/source/corelay/processor/base.py @@ -1,23 +1,30 @@ -"""A module that contains the base classes ``Param`` and ``Processor``.""" - -import inspect +"""A module that contains the abstract base class for all processors, :py:class:`~corelay.processor.base.Processor`, as well as a basic processor, +:py:class:`~corelay.processor.base.FunctionProcessor`, which invokes a specified function. Furthermore, the module contains a function, which ensures +that a specified argument is of type :py:class:`~corelay.processor.base.Processor` and, if it is not, but callable, makes it a +:py:class:`~corelay.processor.base.FunctionProcessor`. +""" + +import collections +import typing from abc import ABC, abstractmethod -from collections import OrderedDict from collections.abc import Callable -from types import FunctionType, LambdaType -from typing import Annotated, Any +from types import FunctionType +from typing import Annotated from corelay.base import Param from corelay.io import NoStorage, NoDataSource, NoDataTarget from corelay.io.storage import Storable from corelay.plugboard import Plugboard +from corelay.utils import get_object_representation class Processor(ABC, Plugboard): - """The abstract base class of processors of tasks in a pipeline instance.""" + """The abstract base class of processors, which perform specific tasks in a :py:class:`corelay.pipeline.base.Pipeline` instance.""" is_output: Annotated[bool, Param(bool, False)] - """Contains a value indicating whether this ``Processor`` is the output of a ``Pipeline``.""" + """Contains a value indicating whether this :py:class:`Processor` is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. + """ is_checkpoint: Annotated[bool, Param(bool, False)] """Contains a value indicating whether check-pointed pipeline computations should start at this point, if there exists a previously computed @@ -25,38 +32,42 @@ class Processor(ABC, Plugboard): """ io: Annotated[Storable, Param(Storable, NoStorage())] - """Contains an IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this run or in subsequent runs - of the ``Pipeline``. + """Contains an IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can then be re-used + in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. """ - checkpoint_data: Any - """If this ``Processor`` is a checkpoint, and if the processor was called at least once, stores the output of this processor.""" + checkpoint_data: typing.Any + """If this :py:class:`Processor` is a checkpoint, and if the processor was called at least once, stores the output of this + processor. + """ def __init__( self, - *args: Any, + *args: typing.Any, is_output: bool | None = None, is_checkpoint: bool | None = None, io: Storable | None = None, - **kwargs: Any + **kwargs: typing.Any ) -> None: - """Initializes a new ``Processor`` instance. All defined ``Param`` class attributes are initialized either to their respective default values - or, if supplied as keyword argument, to the value supplied. + """Initializes a new :py:class:`Processor` instance. All defined :py:class:`~corelay.base.Param` class attributes are + initialized either to their respective default values or, if supplied as keyword argument, to the value supplied. Args: - *args (Any): A list of the positional arguments, which will be used to initialize the parameters of the ``Processor`` that were marked as - positional. - is_output (bool | None, optional): A value indicating whether this ``Processor`` is the output of a ``Pipeline``. If `None` is specified, - the corresponding ``Param`` will default to its defined default value, which is `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. If `None` is specified, the corresponding ``Param`` will default to its defined default - value, which is `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. If `None` is specified, the corresponding ``Param`` will default to its defined default - value, which is an instance of ``NoStorage``. - **kwargs (Any): A dictionary of keyword arguments, which will be used to initialize the parameters of the ``Processor`` that were marked - as keyword arguments. The keys of the dictionary are the names of the parameters, and the values are the values to be assigned to - those parameters. + *args (typing.Any): A :py:class:`list` of the positional arguments, which will be used to initialize the parameters of the + :py:class:`Processor` that were marked as positional. + is_output (bool | None): A value indicating whether this :py:class:`Processor` is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. If :py:obj:`None` is specified, the corresponding + :py:class:`~corelay.base.Param` will default to its defined default value, which is :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. If :py:obj:`None` is specified, the corresponding :py:class:`~corelay.base.Param` + will default to its defined default value, which is :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which + can then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. If :py:obj:`None` is + specified, the corresponding :py:class:`~corelay.base.Param` will default to its defined default value, which is an instance of + :py:class:`corelay.io.NoStorage`. + **kwargs (typing.Any): A :py:class:`dict` of keyword arguments, which will be used to initialize the parameters of the + :py:class:`Processor` that were marked as keyword arguments. The keys of the :py:class:`dict` are the names of the parameters, and the + values are the values to be assigned to those parameters. Raises: TypeError: The number of positional arguments supplied is greater than the number of parameters that were marked as positional or a @@ -88,33 +99,34 @@ def __init__( super().__init__(**parameter_arguments) # Initializes the checkpoint data to None - self.checkpoint_data: Any = None + self.checkpoint_data: typing.Any = None @abstractmethod - def function(self, data: Any) -> Any: - """Applies a function to the input data. This function should be implemented by subclasses of ``Processor``. + def function(self, data: typing.Any) -> typing.Any: + """Applies a function to the input data. This function should be implemented by subclasses of :py:class:`Processor`. Args: - data (Any): The input data to this ``Processor``. + data (typing.Any): The input data to this :py:class:`Processor`. Raises: - NotImplementedError: This is an abstract method and should be implemented by subclasses of ``Processor`` and therefore always raises the - ``NotImplementedError`` exception. + NotImplementedError: This is an abstract method and should be implemented by subclasses of :py:class:`Processor` and therefore always + raises the :py:class:`NotImplementedError` exception. Returns: - Any: Returns the output of the function applied to the input data. + typing.Any: Returns the output of the function applied to the input data. """ raise NotImplementedError('This is an abstract method and should be implemented by subclasses of Processor.') - def __call__(self, data: Any) -> Any: - """Apply ``function`` on the input data and saves the output if the ``is_checkpoint`` ``Param`` was set to `True`. + def __call__(self, data: typing.Any) -> typing.Any: + """Applies :py:meth:`~Processor.function` on the input data and saves the output if the :py:attr:`~Processor.is_checkpoint` + :py:class:`~corelay.base.Param` was set to :py:obj:`True`. Args: - data (Any): The input data to this ``Processor``. + data (typing.Any): The input data to this :py:class:`Processor`. Returns: - Any: Returns the output of the function applied to the input data. + typing.Any: Returns the output of the function applied to the input data. """ try: @@ -129,34 +141,33 @@ def __call__(self, data: Any) -> Any: self.checkpoint_data = out return out - def param_values(self) -> dict[str, Any]: - """Get values for all parameters defined through :obj:`Param` attributes. + def param_values(self) -> dict[str, typing.Any]: + """Get values for all parameters defined through :py:class:`~corelay.base.Param` attributes. Returns: - dict[str, Any]: Returns a dictionary containing the names of the parameters as keys and their values as values. + dict[str, typing.Any]: Returns a :py:class:`dict` containing the names of the parameters as keys and their values as values. """ return self.collect_attr(Param) - def identifiers(self) -> OrderedDict[str, Any]: - """Returns a dict containing the class qualifier name, as well all Parameters marked as identifiers with their - values + def identifiers(self) -> collections.OrderedDict[str, typing.Any]: + """Returns a dict containing the class qualifier name, as well all parameters marked as identifiers with their values. Returns: - OrderedDict[str, Any]: Returns an ordered dictionary, containing the class qualifier name and all parameters marked as identifiers with - their values. + collections.OrderedDict[str, typing.Any]: Returns an :py:class:`collections.OrderedDict`, containing the class qualifier name and all + parameters marked as identifiers with their values. """ - result = OrderedDict(name=type(self).__qualname__) + result = collections.OrderedDict(name=type(self).__qualname__) result.update((key, getattr(self, key)) for key, parameter in self.collect(Param).items() if parameter.is_identifier) return result def copy(self) -> 'Processor': - """Copies this processor, by creating a new ``Processor`` instance with the same values for the parameters defined as ``Param`` class - attributes and the same checkpoint data. + """Copies this processor, by creating a new :py:class:`Processor` instance with the same values for the parameters defined as + :py:class:`~corelay.base.Param` class attributes and the same checkpoint data. Returns: - Processor: Returns a copy of this ``Processor`` instance. + Processor: Returns a copy of this :py:class:`Processor` instance. """ new_processor = type(self)(**self.param_values()) @@ -165,87 +176,82 @@ def copy(self) -> 'Processor': @property def _output_repr(self) -> str: - """Gets a string for the output of the ``Processor``. + """Gets a :py:class:`str` for the output of the :py:class:`Processor`. Returns: - str: Returns the string representation of the output of the ``Processor``. + str: Returns the :py:class:`str` representation of the output of the :py:class:`Processor`. """ - return 'output: numpy.ndarray' + return 'numpy.ndarray' def __repr__(self) -> str: - """Generates a string representation of the ``Processor`` instance, including the class name, the parameters and their values, and the output - representation, e.g., `ProcessorName(metric=sqeuclidean, function=lambda x: x.mean(1)) -> output: numpy.ndarray`. + """Generates a :py:class:`str` representation of the :py:class:`Processor` instance, including the class name, the parameters and their + values, and the output representation, e.g., `ProcessorName(metric=sqeuclidean, function=lambda x: x.mean(1)) -> numpy.ndarray`. Returns: - str: Returns a string representation of the ``Processor`` instance. + str: Returns a :py:class:`str` representation of the :py:class:`Processor` instance. """ - def get_source_code_for_lambda_expression(lambda_expression: LambdaType | Any) -> str: - """Retrieves the source code of the specified lambda expression. If the argument is not a lambda expression, its string representation is - returned. - - Args: - lambda_expression (LambdaType | Any): The lambda expression for which the source code should be retrieved. - - Returns: - str: Returns the source code of the lambda expression as a string, or a string representation of the argument if it is not a lambda - expression. - """ - - if isinstance(lambda_expression, LambdaType): - return inspect.getsource(lambda_expression).split('=', 1)[1].strip() - return str(lambda_expression) - name = self.__class__.__name__ - parameters = ', '.join([f'{k}={get_source_code_for_lambda_expression(v)}' for k, v in self.param_values().items() if v]) + parameters = ', '.join([ + f'{parameter_key}={get_object_representation(parameter_value)}' + for parameter_key, parameter_value + in self.param_values().items() + if parameter_value + ]) return f'{name}({parameters}) -> {self._output_repr}' class FunctionProcessor(Processor): - """A ``Processor`` that executes a user-defined function. + """A :py:class:`Processor` that executes a user-defined function. Args: - processing_function (FunctionType): The function around which to create the :obj:`FunctionProcessor`. This function will be invoked when the - ``function`` method is invoked or the ``FunctionProcessor`` object is called like a function. Depending on whether ``bind_method`` is - `True` or `False`, it wil be bound as a method to the ``FunctionProcessor`` object. - is_output (bool, optional): A value indicating whether this ``FunctionProcessor`` is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - bind_method (bool, optional): A value indicating whether the ``processing_function`` will be bound to this class, enabling it to access - `self`. Defaults to `False`. + processing_function (FunctionType): The function around which to create the :py:class:`FunctionProcessor`. This function will be invoked when + the :py:meth:`~FunctionProcessor.function` method is invoked or the :py:class:`FunctionProcessor` object is called like a function. + Depending on whether :py:attr:`~FunctionProcessor.bind_method` is :py:obj:`True` or :py:obj:`False`, it wil be bound as a method to the + :py:class:`FunctionProcessor` object. + is_output (bool): A value indicating whether this :py:class:`FunctionProcessor` is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + bind_method (bool): A value indicating whether the :py:attr:`~FunctionProcessor.processing_function` will be bound to this class, enabling it + to access `self`. Defaults to :py:obj:`False`. """ processing_function: Annotated[FunctionType, Param(FunctionType, lambda _self, data: data, positional=True)] - """The function around which to create the :obj:`FunctionProcessor`. This function will be invoked when the ``function`` method is invoked or the - ``FunctionProcessor`` object is called like a function. Depending on whether ``bind_method`` is `True` or `False`, it wil be bound as a method to - the ``FunctionProcessor`` object. + """The function around which to create the :py:class:`FunctionProcessor`. This function will be invoked when the + :py:meth:`~FunctionProcessor.function` method is invoked or the :py:class:`FunctionProcessor` object is called like a function. Depending on + whether :py:attr:`~FunctionProcessor.bind_method` is :py:obj:`True` or :py:obj:`False`, it wil be bound as a method to the + :py:class:`FunctionProcessor` object. """ bind_method: Annotated[bool, Param(bool, False)] - """A value indicating whether the ``processing_function`` will be bound to this class, enabling it to access `self`.""" + """A value indicating whether the :py:attr:`~FunctionProcessor.processing_function` will be bound to this class, enabling it to access `self`.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Invokes the function bound to this class with the input data. Note: - In a previous version of CoRelAy, the ``processing_function`` was actually bound to the class in the ``__call__`` method, but this caused - typing issues, as static type checkers like MyPy believed that the ``FunctionProcessor`` class was still abstract, as it did not - explicitly override the ``function`` method. The ``processing_function`` used to be called just ``function``, which meant, that during - runtime, functionally, the ``function`` method was overridden, as its slot would have been taken by the ``function`` parameter. - Statically, however, this was not the case. Overriding the ``function`` method and still binding the ``processing_function`` to the class - in the ``__call__`` method causes more typing issues, as the static type checker does not allow us to write to a method slot. For this - reason, the ``function`` method was overridden and internally calls the ``processing_function`` method with `self` as the first argument. - Functionally, this should be equivalent to the previous version, but it is not guaranteed that it is in every use case. This might have - rethought and changed in the future. + In a previous version of CoRelAy, the :py:attr:`~FunctionProcessor.processing_function` was actually bound to the class in the + :py:meth:`~Processor.__call__` method, but this caused typing issues, as static type checkers like MyPy believed that the + :py:class:`FunctionProcessor` class was still abstract, as it did not explicitly override the :py:meth:`~FunctionProcessor.function` + method. The :py:attr:`~FunctionProcessor.processing_function` used to be called just :py:meth:`~FunctionProcessor.function`, which meant, + that during runtime, functionally, the :py:meth:`~FunctionProcessor.function` method was overridden, as its slot would have been taken by + the :py:meth:`~FunctionProcessor.function` parameter. Statically, however, this was not the case. Overriding the + :py:meth:`~FunctionProcessor.function` method and still binding the :py:attr:`~FunctionProcessor.processing_function` to the class in the + :py:meth:`~Processor.__call__` method causes more typing issues, as the static type checker does not allow us to write to a method slot. + For this reason, the :py:meth:`~FunctionProcessor.function` method was overridden and internally calls the + :py:attr:`~FunctionProcessor.processing_function` method with `self` as the first argument. Functionally, this should be equivalent to the + previous version, but it is not guaranteed that it is in every use case. This might have rethought and changed in the future. Args: - data (Any): The input data to this ``Processor``. + data (typing.Any): The input data to this :py:class:`Processor`. Returns: - Any: Returns the output of the function applied to the input data. + typing.Any: Returns the output of the function applied to the input data. """ if self.bind_method: @@ -253,21 +259,22 @@ def function(self, data: Any) -> Any: return self.processing_function(data) -def ensure_processor(processor_or_callable: Processor | Callable[..., Any], **kwargs: Any) -> Processor: - """Ensures that the specified processor or callable argument ``processor_or_callable`` is of type ``Processor`` and, if it is not, but callable, - make it a ``FunctionProcessor``. Sets the attributes of resulting processor as stated in `**kwargs`. +def ensure_processor(processor_or_callable: Processor | Callable[..., typing.Any], **kwargs: typing.Any) -> Processor: + """Ensures that the specified processor or callable argument ``processor_or_callable`` is of type :py:class:`Processor` and, if it is not, but + callable, make it a :py:class:`FunctionProcessor`. Sets the attributes of resulting processor as stated in `**kwargs`. Args: - processor_or_callable (Processor | Callable[..., Any]): The processor or callable for which to ensure that it is a ``Processor``. - **kwargs (Any): The keyword arguments to be passed to the ``Processor``. These keyword arguments are used to set the values of the parameters - of the ``Processor``. + processor_or_callable (Processor | Callable[..., typing.Any]): The processor or callable for which to ensure that it is a + :py:class:`Processor`. + **kwargs (typing.Any): The keyword arguments to be passed to the :py:class:`Processor`. These keyword arguments are used to set the values of + the parameters of the :py:class:`Processor`. Raises: - TypeError: The supplied processor or callable ``processor_or_callable`` is neither a ``Processor`` nor callable. + TypeError: The supplied processor or callable ``processor_or_callable`` is neither a :py:class:`Processor` nor callable. Returns: - Processor: Returns the original ``processor_or_callable`` if it was a ``Processor``, or a new ``FunctionProcessor``, which calls it if it was - a callable. The attributes of the resulting processor are set as stated in `**kwargs`. + Processor: Returns the original ``processor_or_callable`` if it was a :py:class:`Processor`, or a new :py:class:`FunctionProcessor`, which + calls it if it was a callable. The attributes of the resulting processor are set as stated in `**kwargs`. """ if not isinstance(processor_or_callable, Processor): diff --git a/source/corelay/processor/clustering.py b/source/corelay/processor/clustering.py index 91b4225..04f7893 100644 --- a/source/corelay/processor/clustering.py +++ b/source/corelay/processor/clustering.py @@ -2,65 +2,69 @@ import io import os +import typing from collections.abc import Callable -from typing import Annotated, Any, Literal, SupportsIndex +from typing import Annotated, Literal, SupportsIndex, TypeAlias, TypeGuard, get_args -import scipy.cluster.hierarchy as shc +import numpy +import scipy.cluster.hierarchy +import sklearn.base import sklearn.cluster -from numpy.typing import NDArray -from matplotlib import pyplot as plt -from sklearn.base import ClusterMixin +from matplotlib import pyplot +import corelay.utils from corelay.base import Param from corelay.processor.base import Processor -from corelay.utils import import_or_stub -hdbscan: Callable[..., ClusterMixin] = import_or_stub('hdbscan', 'HDBSCAN') +hdbscan: Callable[..., sklearn.base.ClusterMixin] = corelay.utils.import_or_stub('hdbscan', 'HDBSCAN') """Performs the HDBSCAN clustering algorithm on a vector or distance matrix. -Returns: - ClusterMixin: Returns an HDBSCAN cluster estimator, which can be used to fit the data. - Note: - Since the HDBSCAN library is an optional dependency of CoRelAy, it is imported using the ``import_or_stub`` function, which tries to import the - module/type/function specified. If the import fails, it returns a stub instead, which will raise an exception when used. The exception message - will tell users how to install the missing dependencies for the functionality to work. + Since the HDBSCAN library is an optional dependency of CoRelAy, it is imported using the :py:func:`~corelay.utils.import_or_stub` function, which + tries to import the module/type/function specified. If the import fails, it returns a stub instead, which will raise an exception when used. The + exception message will tell users how to install the missing dependencies for the functionality to work. + +Returns: + sklearn.base.ClusterMixin: Returns an HDBSCAN cluster estimator, which can be used to fit the data. """ class Clustering(Processor): - """The abstract base class for ``Processor`` that performs clustering. + """The abstract base class for :py:class:`~corelay.processor.base.Processor` that performs clustering. Args: - is_output (bool, optional): A value indicating whether this ``Clustering`` processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the clustering algorithm. Defaults to an empty dictionary. + is_output (bool): A value indicating whether this :py:class:`Clustering` processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the clustering algorithm. Defaults to an empty :py:class:`dict`. """ - kwargs: Annotated[dict[str, Any], Param(dict, default={})] - """A dictionary of additional keyword arguments for the clustering algorithm. Defaults to an empty dictionary.""" + kwargs: Annotated[dict[str, typing.Any], Param(dict, default={})] + """A :py:class:`dict` of additional keyword arguments for the clustering algorithm. Defaults to an empty :py:class:`dict`.""" class KMeans(Clustering): - """A clustering ``Processor`` that performs the k-Means clustering algorithm. + """A clustering :py:class:`~corelay.processor.base.Processor` that performs the k-Means clustering algorithm. Args: - is_output (bool, optional): A value indicating whether this ``KMeans`` clustering processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the k-Means clustering algorithm. Defaults to an empty dictionary. - n_clusters (int, optional): The number of clusters to form. Defaults to 2. - index (tuple[int | slice], optional): The indices of the data to be clustered. Defaults to an empty slice. + is_output (bool): A value indicating whether this :py:class:`KMeans` clustering processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the k-Means clustering algorithm. Defaults to an empty :py:class:`dict`. + n_clusters (int): The number of clusters to form. Defaults to 2. + index (tuple[int | slice]): The indices of the data to be clustered. Defaults to an empty slice. See Also: - :obj:`sklearn.cluster.KMeans` + * :py:class:`sklearn.cluster.KMeans` """ n_clusters: Annotated[int, Param(int, 2, identifier=True)] @@ -69,73 +73,42 @@ class KMeans(Clustering): index: Annotated[SupportsIndex | tuple[SupportsIndex, ...], Param((SupportsIndex, tuple), (slice(None),))] """The indices of the data to be clustered. Defaults to an empty slice.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Performs k-Means clustering on the data. Args: - data (Any): The data to be clustered. The data should be a NumPy array or a sparse matrix. + data (typing.Any): The data to be clustered. The data should be a NumPy array or a sparse matrix. Returns: - Any: Returns the a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a - cluster assigned to the data point. + typing.Any: Returns the a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a + cluster assigned to the data point. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return sklearn.cluster.KMeans(n_clusters=self.n_clusters, **self.kwargs).fit_predict(input_data[self.index]) -HDBSCANDistanceMeasure = Literal[ - 'arccos', - 'braycurtis', - 'canberra', - 'chebyshev', - 'cityblock', - 'cosine', - 'dice', - 'euclidean', - 'hamming', - 'haversine', - 'infinity', - 'jaccard', - 'kulsinski', - 'l1', - 'l2', - 'mahalanobis', - 'manhattan', - 'matching', - 'minkowski', - 'p', - 'rogerstanimoto', - 'russellrao', - 'seuclidean', - 'sokalmichener', - 'sokalsneath', - 'wminkowski' -] -"""An enumeration of the distance measures supported by HDBSCAN.""" - - class HDBSCAN(Clustering): - """A clustering ``Processor`` that performs the HDBSCAN clustering algorithm. + """A clustering :py:class:`~corelay.processor.base.Processor` that performs the HDBSCAN clustering algorithm. Args: - is_output (bool, optional): A value indicating whether this ``HDBSCAN`` clustering processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the HDBSCAN clustering algorithm. Defaults to an empty dictionary. - n_clusters (int, optional): The number of clusters to form. Defaults to 2. - metric (HDBSCANDistanceMeasure, optional): The distance metric to use. This can be one of "euclidean", "l2" (equivalent to "euclidean"), - "minkowski", "p" (equivalent to "minkowski"), "manhattan", "cityblock" (equivalent to "manhattan"), "l1" (equivalent to "manhattan"), - "chebyshev", "infinity" (equivalent to "chebyshev"), "seuclidean", "mahalanobis", "wminkowski", "hamming", "canberra", "braycurtis", - "matching", "jaccard", "dice", "kulsinski", "rogerstanimoto", "russellrao", "sokalmichener", "sokalsneath", "haversine", "cosine" (since - the cosine distance is not a true distance measure, it is not supported; using "cosine" will use the "arccos" distance instead), and - "arccos". Defaults to "euclidean". + is_output (bool): A value indicating whether this :py:class:`HDBSCAN` clustering processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the HDBSCAN clustering algorithm. Defaults to an empty :py:class:`dict`. + n_clusters (int): The number of clusters to form. Defaults to 2. + metric (str): The distance metric to use. This can be one of "euclidean", "l2" (equivalent to "euclidean"), "minkowski", "p" (equivalent to + "minkowski"), "manhattan", "cityblock" (equivalent to "manhattan"), "l1" (equivalent to "manhattan"), "chebyshev", "infinity" (equivalent + to "chebyshev"), "seuclidean", "mahalanobis", "wminkowski", "hamming", "canberra", "braycurtis", "matching", "jaccard", "dice", + "kulsinski", "rogerstanimoto", "russellrao", "sokalmichener", "sokalsneath", "haversine", "cosine" (since the cosine distance is not a + true distance measure, it is not supported; using "cosine" will use the "arccos" distance instead), and "arccos". Defaults to "euclidean". See Also: - :obj:`hdbscan.HDBSCAN` + * :py:class:`hdbscan.hdbscan_.HDBSCAN` Notes: GitHub repository including documentation for HDBSCAN: https://github.com/scikit-learn-contrib/hdbscan. @@ -144,64 +117,64 @@ class HDBSCAN(Clustering): n_clusters: Annotated[int, Param(int, 5, identifier=True)] """The number of clusters to form. Defaults to 5.""" - metric: Annotated[HDBSCANDistanceMeasure, Param(str, 'euclidean', identifier=True)] + metric: Annotated[str, Param(str, 'euclidean', identifier=True)] """The distance metric to use. Defaults to "euclidean".""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Performs the HDBSCAN clustering algorithm on the data. Args: - data (Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or a sparse - matrix. + data (typing.Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or a + sparse matrix. Returns: - Any: Returns a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a cluster - assigned to the data point. + typing.Any: Returns a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a + cluster assigned to the data point. """ clustering = hdbscan(min_cluster_size=self.n_clusters, metric=self.metric, **self.kwargs) return clustering.fit_predict(data) -DBSCANDistanceMeasure = Literal[ - 'cityblock', - 'cosine', - 'euclidean', - 'haversine', - 'l1', - 'l2', - 'manhattan', - 'nan_euclidean' - 'precomputed', -] -"""An enumeration of the distance measures supported by DBSCAN.""" - - class DBSCAN(Clustering): - """A clustering ``Processor`` that performs the DBSCAN clustering algorithm. + """A clustering :py:class:`~corelay.processor.base.Processor` that performs the DBSCAN clustering algorithm. Args: - is_output (bool, optional): A value indicating whether this ``DBSCAN`` clustering processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the DBSCAN clustering algorithm. Defaults to an empty dictionary. - metric (str, optional): The distance metric to use. Defaults to "euclidean". - eps (float, optional): The maximum distance between two samples for one to be considered as in the neighborhood of the other. This is not a - maximum bound on the distances of points within a cluster. This is the most important DBSCAN parameter to choose appropriately for your - data set and distance function. Defaults to 0.5. - min_samples (int, optional): The number of samples (or total weight) in a neighborhood for a point to be considered as a core point. This - includes the point itself. If ``min_samples`` is set to a higher value, DBSCAN will find denser clusters, whereas if it is set to a lower - value, the found clusters will be more sparse. Defaults to 5. + is_output (bool): A value indicating whether this :py:class:`DBSCAN` clustering processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the DBSCAN clustering algorithm. Defaults to an empty :py:class:`dict`. + metric (str): The distance metric to use. Defaults to "euclidean". + eps (float): The maximum distance between two samples for one to be considered as in the neighborhood of the other. This is not a maximum + bound on the distances of points within a cluster. This is the most important DBSCAN parameter to choose appropriately for your data set + and distance function. Defaults to 0.5. + min_samples (int): The number of samples (or total weight) in a neighborhood for a point to be considered as a core point. This includes the + point itself. If ``min_samples`` is set to a higher value, DBSCAN will find denser clusters, whereas if it is set to a lower value, the + found clusters will be more sparse. Defaults to 5. See Also: - :obj:`sklearn.cluster.DBSCAN` + * :py:class:`sklearn.cluster.DBSCAN` """ - metric: Annotated[DBSCANDistanceMeasure, Param(str, 'euclidean', identifier=True)] - """The distance metric to use. Defaults to "euclidean".""" + metric: Annotated[str, Param(str, 'euclidean', identifier=True)] + """The distance metric to use. Can be one of + + * "cityblock" + * "cosine" + * "euclidean" + * "haversine" + * "l1" + * "l2" + * "manhattan" + * "nan_euclidean" + * "precomputed". + + Defaults to "euclidean". + """ eps: Annotated[float, Param(float, 0.5, identifier=True)] """The maximum distance between two samples for one to be considered as in the neighborhood of the other. This is not a maximum bound on the @@ -211,20 +184,20 @@ class DBSCAN(Clustering): min_samples: Annotated[int, Param(int, 5, identifier=True)] """The number of samples (or total weight) in a neighborhood for a point to be considered as a core point. This includes the point itself. If - ``min_samples`` is set to a higher value, DBSCAN will find denser clusters, whereas if it is set to a lower value, the found clusters will be more - sparse. Defaults to 5. + :py:attr:`DBSCAN.min_samples` is set to a higher value, DBSCAN will find denser clusters, whereas if it is set to a lower value, the found + clusters will be more sparse. Defaults to 5. """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Performs the DBSCAN clustering algorithm on the data. Args: - data (Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or a sparse - matrix. + data (typing.Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or a + sparse matrix. Returns: - Any: Returns a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a cluster - assigned to the data point. Noisy points will be labeled as -1. + typing.Any: Returns a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a + cluster assigned to the data point. Noisy points will be labeled as -1. """ clustering = sklearn.cluster.DBSCAN( @@ -236,69 +209,66 @@ def function(self, data: Any) -> Any: return clustering.fit_predict(data) -AgglomerativeClusteringDistanceMeasure = Literal[ - 'euclidean', - 'l1', - 'l2', - 'manhattan', - 'cosine', - 'precomputed' -] -"""An enumeration of the distance measures supported by the Agglomerative Clustering algorithm.""" - - -AgglomerativeClusteringLinkageMethod = Literal['ward', 'complete', 'average', 'single'] -"""An enumeration of the linkage method supported by the Agglomerative Clustering algorithm. The linkage method is used to determine the distance -between two clusters when performing hierarchical clustering. The Agglomerative Clustering algorithm will merge the pairs of clusters that minimize -this method. The following linkage methods are supported: - -- "ward" minimizes the variance of the clusters being merged. -- "average" uses the average of the distances of each observation of the two clusters. -- "complete" uses the maximum distances between all observations of the two clusters. -- "single" uses the minimum of the distances between all observations of the two clusters. -""" - - class AgglomerativeClustering(Clustering): - """A clustering ``Processor`` that performs the Agglomerative Clustering algorithm. + """A clustering :py:class:`~corelay.processor.base.Processor` that performs the Agglomerative Clustering algorithm. Args: - is_output (bool, optional): A value indicating whether this ``AgglomerativeClustering`` clustering processor is the output of a ``Pipeline``. - Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the agglomerative clustering algorithm. Defaults to an empty dictionary. - n_clusters (int, optional): The number of clusters to form. Defaults to 5. - metric (AgglomerativeClusteringDistanceMeasure, optional): The distance metric to use. Defaults to "euclidean". - linkage (AgglomerativeClusteringLinkageMethod, optional): The linkage method to use. This determines which distance to use between two newly - formed clusters. The algorithm will merge the pairs of clusters that minimize this method. Defaults to "ward". + is_output (bool): A value indicating whether this :py:class:`AgglomerativeClustering` clustering processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the agglomerative clustering algorithm. Defaults to an empty + :py:class:`dict`. + n_clusters (int): The number of clusters to form. Defaults to 5. + metric (str): The distance metric to use. Defaults to "euclidean". + linkage (str): The linkage method to use. This determines which distance to use between two newly formed clusters. The algorithm will merge + the pairs of clusters that minimize this method. Defaults to "ward". See Also: - :obj:`sklearn.cluster.AgglomerativeClustering` + * :py:class:`sklearn.cluster.AgglomerativeClustering` """ n_clusters: Annotated[int, Param(int, 5, identifier=True)] """The number of clusters to form. Defaults to 5.""" - metric: Annotated[AgglomerativeClusteringDistanceMeasure, Param(str, 'euclidean', identifier=True)] - """The distance metric to use. Defaults to "euclidean".""" + metric: Annotated[str, Param(str, 'euclidean', identifier=True)] + """The distance metric to use. Can be one of + + * "euclidean" + * "l1" + * "l2" + * "manhattan" + * "cosine" + * "precomputed". - linkage: Annotated[AgglomerativeClusteringLinkageMethod, Param(str, 'ward', identifier=True)] + Defaults to "euclidean". + """ + + linkage: Annotated[str, Param(str, 'ward', identifier=True)] """The linkage method to use. This determines which distance to use between two newly formed clusters. The algorithm will merge the pairs of - clusters that minimize this method. Defaults to "ward". + clusters that minimize this method. Can be one of + + * "ward" minimizes the variance of the clusters being merged. + * "average" uses the average of the distances of each observation of the two clusters. + * "complete" uses the maximum distances between all observations of the two clusters. + * "single" uses the minimum of the distances between all observations of the two clusters. + + Defaults to "ward". """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Performs the Agglomerative Clustering algorithm on the data. Args: - data (Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or + data (typing.Any): The data to be clustered. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or `(number_of_samples, number_of_samples)`, or a sparse matrix. Returns: - Any: _description_ + typing.Any: Returns a NumPy array of shape `(number_of_samples,)`, that contains the cluster labels, where each label corresponds to a + cluster assigned to the data point. """ clustering = sklearn.cluster.AgglomerativeClustering( @@ -310,93 +280,81 @@ def function(self, data: Any) -> Any: return clustering.fit_predict(data) -DendrogramDistanceMeasure = Literal[ - 'braycurtis', - 'canberra', - 'chebyshev', - 'cityblock', - 'correlation', - 'cosine', - 'dice', - 'euclidean', - 'hamming', - 'jaccard', - 'jensenshannon', - 'kulczynski1', - 'mahalanobis', - 'minkowski', - 'rogerstanimoto', - 'russellrao', - 'seuclidean', - 'sokalmichener', - 'sokalsneath', - 'sqeuclidean', - 'yule' -] -"""An enumeration of the distance measures supported by the dendrogram clustering ``Processor``.""" - - -DendrogramLinkageMethod = Literal['ward', 'average', 'complete', 'single', 'centroid', 'median', 'weighted'] -"""An enumeration of the linkage method supported by the hierarchical clustering algorithm used by the Dendrogram ``Processor``. The linkage method is -used to determine the distance between two newly formed clusters when performing hierarchical clustering. The hierarchical clustering algorithm used -by the Dendrogram ``Processor`` will merge the pairs of clusters that minimize this method. The following linkage methods are supported: - -- "ward" minimizes the variance of the clusters being merged. -- "average" uses the average of the distances of each observation of the two clusters. -- "complete" uses the maximum distances between all observations of the two clusters. -- "single" uses the minimum of the distances between all observations of the two clusters. -- "centroid" the centroid of the new cluster that would be formed by merging the two clusters. -- "median" uses the median of the centroids of the two clusters. -- "weighted" assigns the weighted distance between the two original clusters and a third remaining cluster to the new cluster. -""" - - class Dendrogram(Clustering): - """A clustering ``Processor`` that generates plots the hierarchical clustering as a dendrogram. + """A clustering :py:class:`~corelay.processor.base.Processor` that generates plots the hierarchical clustering as a dendrogram. Args: - is_output (bool, optional): A value indicating whether this ``Dendrogram`` clustering processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the hierarchical clustering algorithm. Defaults to an empty dictionary. - output_file (str | bytes | os.PathLike[Any] | io.IOBase): The path to a file or a file descriptor to save the dendrogram plot to. - metric (DendrogramDistanceMeasure, optional): The distance metric to use for the clustering. Defaults to "euclidean". - linkage (DendrogramLinkageMethod, optional): The linkage criterion to use. This determines which distance to use between sets of observation. - Defaults to "ward". + is_output (bool): A value indicating whether this :py:class:`Dendrogram` clustering processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the hierarchical clustering algorithm. Defaults to an empty :py:class:`dict`. + output_file (str | io.IOBase): The path to a file or a file descriptor to save the dendrogram plot to. + metric (str): The distance metric to use for the clustering. Defaults to "euclidean". + linkage (str): The linkage criterion to use. This determines which distance to use between sets of observation. Defaults to "ward". """ - output_file: Annotated[str | bytes | os.PathLike[Any] | io.IOBase, Param((str, bytes, os.PathLike, io.IOBase), mandatory=True)] + output_file: Annotated[str | io.IOBase, Param((str, io.IOBase), mandatory=True)] """The path to a file or a file descriptor to save the dendrogram plot to.""" - metric: Annotated[DendrogramDistanceMeasure, Param(str, 'euclidean')] + metric: Annotated[str, Param(str, 'euclidean')] """The distance metric to use for the clustering. This can be one of "braycurtis", "canberra", "chebyshev", "cityblock", "correlation", "cosine", "dice", "euclidean", "hamming", "jaccard", "jensenshannon", "kulczynski1", "mahalanobis", "minkowski", "rogerstanimoto", "russellrao", "seuclidean", "sokalmichener", "sokalsneath", "sqeuclidean", or "yule". Defaults to "euclidean". """ - linkage: Annotated[DendrogramLinkageMethod, Param(str, 'ward')] - """The linkage criterion to use. This determines which distance to use between sets of observation. Defaults to "ward".""" + linkage: Annotated[str, Param(str, 'ward')] + """The linkage method used by the Dendrogram :py:class:`~corelay.processor.base.Processor`. The linkage method is used to determine the distance + between two newly formed clusters when performing hierarchical clustering. The hierarchical clustering algorithm used by the Dendrogram + :py:class:`~corelay.processor.base.Processor` will merge the pairs of clusters that minimize this method. The following linkage methods + are supported: + + * "ward" minimizes the variance of the clusters being merged. + * "average" uses the average of the distances of each observation of the two clusters. + * "complete" uses the maximum distances between all observations of the two clusters. + * "single" uses the minimum of the distances between all observations of the two clusters. + * "centroid" the centroid of the new cluster that would be formed by merging the two clusters. + * "median" uses the median of the centroids of the two clusters. + * "weighted" assigns the weighted distance between the two original clusters and a third remaining cluster to the new cluster. + + Defaults to "ward". + """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Performs the hierarchical clustering algorithm on the data and generates a dendrogram plot. Args: - data (Any): The data to be clustered. The data should be a NumPy array that contains a condensed distance matrix. A condensed distance - matrix is a flat array containing the upper triangular of the distance matrix. Alternatively, an array of shape + data (typing.Any): The data to be clustered. The data should be a NumPy array that contains a condensed distance matrix. A condensed + distance matrix is a flat array containing the upper triangular of the distance matrix. Alternatively, an array of shape `(number_of_observations, number_of_dimensions)` may be passed in. + Raises: + ValueError: The linkage method is invalid. + Returns: - Any: Returns the data that was passed in. The data is not modified. + typing.Any: Returns the data that was passed in. The data is not modified. """ - if isinstance(self.output_file, (str, bytes, os.PathLike)): + # This is necessary to ensure that MyPy does not complain that the linkage method is not valid; ideally, we would use literals ourselves, but + # unfortunately, Sphinx AutoDoc cannot handle type aliases correctly unless we use Postponed Evaluation of Annotations (PEP 563), which in + # turn breaks our usage of typing.Annotated for slots + LinkageMethod: TypeAlias = Literal['single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward'] + linkage_methods = list(get_args(LinkageMethod)) + + def check_if_linkage_method_is_valid(linkage: str) -> TypeGuard[LinkageMethod]: + return linkage in linkage_methods + + if not check_if_linkage_method_is_valid(self.linkage): + raise ValueError(f'Invalid linkage method: {self.linkage}.') + + if isinstance(self.output_file, str): os.makedirs(os.path.dirname(self.output_file), exist_ok=True) - plt.figure(figsize=(10, 7)) - shc.dendrogram(shc.linkage(data, method=self.linkage)) - plt.savefig(self.output_file) + pyplot.figure(figsize=(10, 7)) + scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(data, method=self.linkage)) + pyplot.savefig(self.output_file) return data diff --git a/source/corelay/processor/distance.py b/source/corelay/processor/distance.py index 6c3a2b3..cdaff9b 100644 --- a/source/corelay/processor/distance.py +++ b/source/corelay/processor/distance.py @@ -1,8 +1,9 @@ -"""A module that contains pair-wise distance ``Processors``.""" +"""A module that contains processors for pair-wise distance metrics.""" -from typing import Annotated, Any, Literal +import typing +from typing import Annotated, Literal, TypeAlias, TypeGuard, get_args -from numpy.typing import NDArray +import numpy from scipy.spatial.distance import pdist, squareform from corelay.base import Param @@ -13,72 +14,140 @@ class Distance(Processor): """The abstract base class for distance processors. Args: - is_output (bool, optional): A value indicating whether this ``Distance`` processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`Distance` processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. """ -PairWiseDistanceMeasure = Literal[ - 'braycurtis', - 'canberra', - 'chebychev', 'chebyshev', 'cheby', 'cheb', 'ch', - 'cityblock', 'cblock', 'cb', 'c', - 'correlation', 'co', - 'cosine', 'cos', - 'dice', - 'euclidean', 'euclid', 'eu', 'e', - 'hamming', 'hamm', 'ha', 'h', - 'minkowski', 'mi', 'm', - 'pnorm', - 'jaccard', 'jacc', 'ja', 'j', - 'jensenshannon', 'js', - 'kulczynski1', - 'mahalanobis', 'mahal', 'mah', - 'rogerstanimoto', - 'russellrao', - 'seuclidean', 'se', 's', - 'sokalmichener', - 'sokalsneath', - 'sqeuclidean', 'sqe', 'sqeuclid', - 'yule' -] -"""An enumeration of the distance measures supported by ``scipy.spatial.distance.pdist``.""" - - class SciPyPDist(Distance): - """A distance metric, that computes the pair-wise distance between observations in n-dimensional space using ``scipy.spatial.distance.pdist``. + """A distance metric, that computes the pair-wise distance between observations in n-dimensional space using + :py:func:`scipy.spatial.distance.pdist`. Args: - is_output (bool, optional): A value indicating whether this ``SciPyPDist`` distance processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`SciPyPDist` distance processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. metric (str): The distance metric to use. Default is "euclidean". m_kwargs (dict): Additional keyword arguments to pass to the distance function. """ - metric: Annotated[PairWiseDistanceMeasure, Param(str, 'euclidean', identifier=True)] - """The distance metric to use. Defaults to "euclidean".""" + metric: Annotated[str, Param(str, 'euclidean', identifier=True)] + """The distance metric to use. Can be one of + + * "braycurtis" + * "canberra" + * "chebychev", "chebyshev", "cheby", "cheb", "ch" + * "cityblock", "cblock", "cb", "c" + * "correlation", "co" + * "cosine", "cos" + * "dice" + * "euclidean", "euclid", "eu", "e" + * "hamming", "hamm", "ha", "h" + * "minkowski", "mi", "m" + * "pnorm" + * "jaccard", "jacc", "ja", "j" + * "jensenshannon", "js" + * "kulczynski1" + * "mahalanobis", "mahal", "mah" + * "rogerstanimoto" + * "russellrao" + * "seuclidean", "se", "s" + * "sokalmichener" + * "sokalsneath" + * "sqeuclidean", "sqe", "sqeuclid" + * "yule" + + Defaults to "euclidean". + """ - m_kwargs: Annotated[dict[str, Any], Param(dict, {})] + m_kwargs: Annotated[dict[str, typing.Any], Param(dict, {})] """Additional keyword arguments to pass to the distance function.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Applies the pairwise distance function to the input data. Args: - data (Any): The input data that is to be processed. The input data should be a NumPy array of shape + data (typing.Any): The input data that is to be processed. The input data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + Raises: + ValueError: The distance metric is not valid. + Returns: - Any: Returns the pairwise distance matrix of shape `(number_of_samples, number_of_samples)`. + typing.Any: Returns the pairwise distance matrix of shape `(number_of_samples, number_of_samples)`. """ - input_data: NDArray[Any] = data - distance_matrix: NDArray[Any] = pdist(input_data, metric=self.metric, **self.m_kwargs) + # This is necessary to ensure that MyPy does not complain that the metric is not valid; ideally, we would use literals ourselves, but + # unfortunately, Sphinx AutoDoc cannot handle type aliases correctly unless we use Postponed Evaluation of Annotations (PEP 563), which in + # turn breaks our usage of typing.Annotated for slots + Metric: TypeAlias = Literal[ + 'braycurtis', + 'canberra', + 'chebychev', + 'chebyshev', + 'cheby', + 'cheb', + 'ch', + 'cityblock', + 'cblock', + 'cb', + 'c', + 'correlation', + 'co', + 'cosine', + 'cos', + 'dice', + 'euclidean', + 'euclid', + 'eu', + 'e', + 'hamming', + 'hamm', + 'ha', + 'h', + 'minkowski', + 'mi', + 'm', + 'pnorm', + 'jaccard', + 'jacc', + 'ja', + 'j', + 'jensenshannon', + 'js', + 'kulczynski1', + 'mahalanobis', + 'mahal', + 'mah', + 'rogerstanimoto', + 'russellrao', + 'seuclidean', + 'se', + 's', + 'sokalmichener', + 'sokalsneath', + 'sqeuclidean', + 'sqe', + 'sqeuclid', + 'yule' + ] + metrics = list(get_args(Metric)) + + def check_if_metric_is_valid(metric: str) -> TypeGuard[Metric]: + return metric in metrics + + if not check_if_metric_is_valid(self.metric): + raise ValueError(f'Invalid metric: {self.metric}.') + + input_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating] | numpy.dtype[numpy.integer]] = data + distance_matrix: numpy.ndarray[typing.Any, typing.Any] = pdist(input_data, metric=self.metric, **self.m_kwargs) return squareform(distance_matrix) diff --git a/source/corelay/processor/embedding.py b/source/corelay/processor/embedding.py index 4e30ba2..9c40d23 100644 --- a/source/corelay/processor/embedding.py +++ b/source/corelay/processor/embedding.py @@ -1,32 +1,31 @@ """A module that contains processors for embedding algorithms.""" +import typing from collections.abc import Callable -from typing import Annotated, Any, Literal +from typing import Annotated, Literal, TypeAlias, TypeGuard, get_args import numpy -from numpy.typing import NDArray +import sklearn.base from scipy.sparse.linalg import eigsh -from sklearn.base import TransformerMixin from sklearn.decomposition import PCA from sklearn.manifold import TSNE, LocallyLinearEmbedding +import corelay.utils from corelay.base import Param from corelay.processor.base import Processor -from corelay.processor.distance import PairWiseDistanceMeasure -from corelay.utils import import_or_stub -UMAP: Callable[..., TransformerMixin] = import_or_stub('umap', 'UMAP') +UMAP: Callable[..., sklearn.base.TransformerMixin] = corelay.utils.import_or_stub('umap', 'UMAP') """Performs the Uniform Manifold Approximation and Projection (UMAP) dimensionality reduction algorithm, which will find a low dimensional embedding of the data that approximates an underlying manifold. -Returns: - TransformerMixin: Returns a UMAP cluster estimator, which can be used to fit the data. - Note: - Since the UMAP library is an optional dependency of CoRelAy, it is imported using the ``import_or_stub`` function, which tries to import the - module/type/function specified. If the import fails, it returns a stub instead, which will raise an exception when used. The exception message - will tell users how to install the missing dependencies for the functionality to work. + Since the UMAP library is an optional dependency of CoRelAy, it is imported using the :py:func:`corelay.utils.import_or_stub` function, which + tries to import the module/type/function specified. If the import fails, it returns a stub instead, which will raise an exception when used. The + exception message will tell users how to install the missing dependencies for the functionality to work. + +Returns: + sklearn.base.TransformerMixin: Returns a UMAP cluster estimator, which can be used to fit the data. """ @@ -34,86 +33,101 @@ class Embedding(Processor): """The abstract base class for embedding processors. Args: - is_output (bool, optional): A value indicating whether this ``Embedding`` processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the embedding algorithm. Defaults to an empty dictionary. + is_output (bool): A value indicating whether this :py:class:`Embedding` processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the embedding algorithm. Defaults to an empty :py:class:`dict`. """ - kwargs: Annotated[dict[str, Any], Param(dict, {})] + kwargs: Annotated[dict[str, typing.Any], Param(dict, {})] """Additional keyword arguments to pass to the embedding function.""" -EigenvalueType = Literal['LM', 'SM', 'LA', 'SA', 'BE'] -"""The type of eigenvalues and eigenvectors to compute. The options are: - -- "LM": Largest (in magnitude) eigenvalues. -- "SM": Smallest (in magnitude) eigenvalues. -- "LA": Largest (algebraic) eigenvalues. -- "SA": Smallest (algebraic) eigenvalues. -- "BE": Half (k/2) from each end of the spectrum. - -Note: - If the input is a complex Hermitian matrix, 'BE' is invalid. -""" - - class EigenDecomposition(Embedding): - """A spectral embedding ``Processor`` that performs eigenvalue decomposition. + """A spectral embedding :py:class:`~corelay.processor.base.Processor` that performs eigenvalue decomposition. Args: - is_output (bool, optional): A value indicating whether this ``EigenDecomposition`` embedding processor is the output of a ``Pipeline``. - Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the eigenvalue decomposition embedding algorithm. Defaults to an empty - n_eigval (int, optional): The number of eigenvalues and eigenvectors to compute. Defaults to 32. - which (str, optional): The type of eigenvalues and eigenvectors to compute. Defaults to "LM" (largest in magnitude). - normalize (bool, optional): A value indicating whether to normalize the eigenvectors. Defaults to `True`. - dictionary. + is_output (bool): A value indicating whether this :py:class:`EigenDecomposition` embedding processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the eigenvalue decomposition embedding algorithm. Defaults to an empty + n_eigval (int): The number of eigenvalues and eigenvectors to compute. Defaults to 32. + which (str): The type of eigenvalues and eigenvectors to compute. Defaults to "LM" (largest in magnitude). + normalize (bool): A value indicating whether to normalize the eigenvectors. Defaults to :py:obj:`True`. """ n_eigval: Annotated[int, Param(int, 32, identifier=True)] """The number of eigenvalues and eigenvectors to compute. Defaults to 32.""" - which: Annotated[EigenvalueType, Param(str, 'LM')] - """The type of eigenvalues and eigenvectors to compute. Defaults to "LM" (largest in magnitude).""" + which: Annotated[str, Param(str, 'LM')] + """The type of eigenvalues and eigenvectors to compute. The options are: + + * "LM": Largest (in magnitude) eigenvalues. + * "SM": Smallest (in magnitude) eigenvalues. + * "LA": Largest (algebraic) eigenvalues. + * "SA": Smallest (algebraic) eigenvalues. + * "BE": Half (k/2) from each end of the spectrum. + + Defaults to "LM" (largest in magnitude). + + Note: + If the input is a complex Hermitian matrix, 'BE' is invalid. + """ normalize: Annotated[bool, Param(bool, True, identifier=True)] """A value indicating whether to normalize the eigenvectors. Defaults to True.""" @property def _output_repr(self) -> str: - """Gets a string representation of the output of the function. + """Gets a :py:class:`str` representation of the output of the function. Returns: - str: Returns a string representation of the output of the function. + str: Returns a :py:class:`str` representation of the output of the function. """ return '(eigval: numpy.ndarray, eigvec: numpy.ndarray)' - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the spectral embedding of the input data using eigenvalue decomposition. - Args: - data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. - - Returns: - Any: Returns a tuple containing the eigenvalues and eigenvectors of the input data. - Note: We use the fact that (I-A)v = (1-λ)v and thus compute the largest eigenvalues of the identity minus the data and return one minus the eigenvalue. + + Args: + data (typing.Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + + Raises: + ValueError: The eigenvalue and eigenvector type is not valid. + + Returns: + typing.Any: Returns a tuple containing the eigenvalues and eigenvectors of the input data. """ - input_data: NDArray[Any] = data - eigenvalues: NDArray[numpy.float64] - eigenvectors: NDArray[numpy.float64] - eigenvalues, eigenvectors = eigsh(input_data, k=self.n_eigval, which=self.which, **self.kwargs) # type: ignore[arg-type] + # This is necessary to ensure that MyPy does not complain that the "which" argument is not valid; ideally, we would use literals ourselves, + # but unfortunately, Sphinx AutoDoc cannot handle type aliases correctly unless we use Postponed Evaluation of Annotations (PEP 563), which in + # turn breaks our usage of typing.Annotated for slots + EigenvalueAndEigenvectorType: TypeAlias = Literal['LM', 'SM', 'LR', 'SR', 'LI', 'SI'] + eigenvalue_and_eigenvector_types = list(get_args(EigenvalueAndEigenvectorType)) + + def check_if_eigenvalue_and_eigenvector_type_is_valid(eigenvalue_and_eigenvector_type: str) -> TypeGuard[EigenvalueAndEigenvectorType]: + return eigenvalue_and_eigenvector_type in eigenvalue_and_eigenvector_types + + if not check_if_eigenvalue_and_eigenvector_type_is_valid(self.which): + raise ValueError(f'Invalid eigenvalue and eigenvector type: {self.which}.') + + input_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.floating] | numpy.dtype[numpy.integer]] = data + eigenvalues: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] + eigenvectors: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] + eigenvalues, eigenvectors = eigsh(input_data, k=self.n_eigval, which=self.which, **self.kwargs) eigenvalues = 1.0 - eigenvalues if self.normalize: @@ -123,27 +137,54 @@ def function(self, data: Any) -> Any: class TSNEEmbedding(Embedding): - """An embedding ``Processor`` that uses the t-SNE algorithm to reduce the dimensionality of the input data. + """An embedding :py:class:`~corelay.processor.base.Processor` that uses the t-SNE algorithm to reduce the dimensionality of the input data. Args: - is_output (bool, optional): A value indicating whether this ``TSNEEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the t-SNE embedding algorithm. Defaults to an empty dictionary. - n_components (int, optional): The number of dimensions to reduce the data to. Defaults to 2. - metric (str, optional): The distance metric to use. Defaults to "euclidean". - perplexity (float, optional): The perplexity parameter for the t-SNE algorithm. Defaults to 30. - early_exaggeration (float, optional): The early exaggeration parameter for the t-SNE algorithm. Defaults to 12. + is_output (bool): A value indicating whether this :py:class:`TSNEEmbedding` embedding processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the t-SNE embedding algorithm. Defaults to an empty :py:class:`dict`. + n_components (int): The number of dimensions to reduce the data to. Defaults to 2. + metric (str): The distance metric to use. Defaults to "euclidean". + perplexity (float): The perplexity parameter for the t-SNE algorithm. Defaults to 30. + early_exaggeration (float): The early exaggeration parameter for the t-SNE algorithm. Defaults to 12. """ n_components: Annotated[int, Param(int, default=2, identifier=True)] """The number of dimensions to reduce the data to. Defaults to 2.""" - metric: Annotated[PairWiseDistanceMeasure, Param(str, default='euclidean', identifier=True)] - """The distance metric to use. Defaults to "euclidean".""" + metric: Annotated[str, Param(str, default='euclidean', identifier=True)] + """The distance metric to use. Can be one of + + * "braycurtis" + * "canberra" + * "chebychev", "chebyshev", "cheby", "cheb", "ch" + * "cityblock", "cblock", "cb", "c" + * "correlation", "co" + * "cosine", "cos" + * "dice" + * "euclidean", "euclid", "eu", "e" + * "hamming", "hamm", "ha", "h" + * "minkowski", "mi", "m" + * "pnorm" + * "jaccard", "jacc", "ja", "j" + * "jensenshannon", "js" + * "kulczynski1" + * "mahalanobis", "mahal", "mah" + * "rogerstanimoto" + * "russellrao" + * "seuclidean", "se", "s" + * "sokalmichener" + * "sokalsneath" + * "sqeuclidean", "sqe", "sqeuclid" + * "yule" + + Defaults to "euclidean". + """ perplexity: Annotated[float, Param(float, default=30.0, identifier=True)] """The perplexity parameter for the t-SNE algorithm. Defaults to 30.""" @@ -151,15 +192,15 @@ class TSNEEmbedding(Embedding): early_exaggeration: Annotated[float, Param(float, default=12.0, identifier=True)] """The early exaggeration parameter for the t-SNE algorithm. Defaults to 12.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the t-SNE embedding of the input data. Args: - data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or + data (typing.Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)` or `(number_of_samples, number_of_samples)`. Returns: - Any: Returns the t-SNE embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. + typing.Any: Returns the t-SNE embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. """ tsne = TSNE( @@ -174,34 +215,36 @@ def function(self, data: Any) -> Any: class PCAEmbedding(Embedding): - """An embedding ``Processor`` that uses the principal component analysis (PCA) algorithm to reduce the dimensionality of the input data. + """An embedding :py:class:`~corelay.processor.base.Processor` that uses the principal component analysis (PCA) algorithm to reduce the + dimensionality of the input data. Args: - is_output (bool, optional): A value indicating whether this ``PCAEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the PCA embedding algorithm. Defaults to an empty dictionary. - n_components (int, optional): The number of dimensions to reduce the data to. Defaults to 2. - whiten (bool, optional): A value indicating whether to whiten the data. Defaults to `False`. + is_output (bool): A value indicating whether this :py:class:`PCAEmbedding` embedding processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the PCA embedding algorithm. Defaults to an empty :py:class:`dict`. + n_components (int): The number of dimensions to reduce the data to. Defaults to 2. + whiten (bool): A value indicating whether to whiten the data. Defaults to :py:obj:`False`. """ n_components: Annotated[int, Param(int, default=2, identifier=True)] """The number of dimensions to reduce the data to. Defaults to 2.""" whiten: Annotated[bool, Param(bool, default=False, identifier=True)] - """A value indicating whether to whiten the data. Defaults to `False`.""" + """A value indicating whether to whiten the data. Defaults to :py:obj:`False`.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the PCA embedding of the input data. Args: - data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + data (typing.Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. Returns: - Any: Returns the PCA embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. + typing.Any: Returns the PCA embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. """ pca = PCA( @@ -213,18 +256,20 @@ def function(self, data: Any) -> Any: class LLEEmbedding(Embedding): - """An embedding ``Processor`` that uses the locally linear embedding (LLE) algorithm to reduce the dimensionality of the input data. + """An embedding :py:class:`~corelay.processor.base.Processor` that uses the locally linear embedding (LLE) algorithm to reduce the dimensionality + of the input data. Args: - is_output (bool, optional): A value indicating whether this ``LLEEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the LLE embedding algorithm. Defaults to an empty dictionary. - n_components (int, optional): The number of dimensions to reduce the data to. Defaults to 2. - n_neighbors (int, optional): The number of neighbors to use for the LLE algorithm. Defaults to 5. + is_output (bool): A value indicating whether this :py:class:`LLEEmbedding` embedding processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the LLE embedding algorithm. Defaults to an empty :py:class:`dict`. + n_components (int): The number of dimensions to reduce the data to. Defaults to 2. + n_neighbors (int): The number of neighbors to use for the LLE algorithm. Defaults to 5. """ n_components: Annotated[int, Param(int, default=2, identifier=True)] @@ -233,14 +278,14 @@ class LLEEmbedding(Embedding): n_neighbors: Annotated[int, Param(int, default=5, identifier=True)] """The number of neighbors to use for the LLE algorithm. Defaults to 5.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the LLE embedding of the input data. Args: - data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + data (typing.Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. Returns: - Any: Returns the LLE embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. + typing.Any: Returns the LLE embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_components)`. """ lle = LocallyLinearEmbedding( @@ -251,49 +296,22 @@ def function(self, data: Any) -> Any: return lle.fit_transform(data) -UMAPDistanceMetric = Literal[ - 'euclidean', - 'manhattan', - 'chebyshev', - 'minkowski', - 'canberra', - 'braycurtis', - 'mahalanobis', - 'wminkowski', - 'seuclidean', - 'cosine', - 'correlation', - 'haversine', - 'hamming', - 'jaccard', - 'dice', - 'russelrao', - 'kulsinski', - 'll_dirichlet', - 'hellinger', - 'rogerstanimoto', - 'sokalmichener', - 'sokalsneath', - 'yule' -] -"""An enumeration of the distance measures supported by ``umap.UMAP``.""" - - class UMAPEmbedding(Embedding): - """An embedding ``Processor`` that uses the Uniform Manifold Approximation and Projection (UMAP) algorithm to reduce the dimensionality of the - input data. + """An embedding :py:class:`~corelay.processor.base.Processor` that uses the Uniform Manifold Approximation and Projection (UMAP) algorithm to + reduce the dimensionality of the input data. Args: - is_output (bool, optional): A value indicating whether this ``UMAPEmbedding`` embedding processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict[str, Any], optional): Additional keyword arguments for the UMAP embedding algorithm. Defaults to an empty dictionary. - n_neighbors (int, optional): The number of neighbors to use for the UMAP algorithm. Defaults to 15. - min_dist (float, optional): The minimum distance between points in the UMAP algorithm. Defaults to 0.1. - metric (str, optional): The distance metric to use for the UMAP algorithm. Defaults to "correlation". + is_output (bool): A value indicating whether this :py:class:`UMAPEmbedding` embedding processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict[str, typing.Any]): Additional keyword arguments for the UMAP embedding algorithm. Defaults to an empty :py:class:`dict`. + n_neighbors (int): The number of neighbors to use for the UMAP algorithm. Defaults to 15. + min_dist (float): The minimum distance between points in the UMAP algorithm. Defaults to 0.1. + metric (str): The distance metric to use for the UMAP algorithm. Defaults to "correlation". """ n_neighbors: Annotated[int, Param(int, default=15, identifier=True)] @@ -302,20 +320,23 @@ class UMAPEmbedding(Embedding): min_dist: Annotated[float, Param(float, default=0.1, identifier=True)] """The minimum distance between points in the UMAP algorithm. Defaults to 0.1.""" - metric: Annotated[UMAPDistanceMetric, Param(str, default='correlation', identifier=True)] - """The distance metric to use for the UMAP algorithm. Defaults to "correlation".""" + metric: Annotated[str, Param(str, default='correlation', identifier=True)] + """The distance metric to use for the UMAP algorithm. This can be one of "euclidean", "manhattan", "chebyshev", "minkowski", "canberra", + "braycurtis", "mahalanobis", "wminkowski", "seuclidean", "cosine", "correlation", "haversine", "hamming", "jaccard", "dice", "russelrao", + "kulsinski", "ll_dirichlet", "hellinger", "rogerstanimoto", "sokalmichener", "sokalsneath", or "yule" Defaults to "correlation". + """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the UMAP embedding of the input data. + Note: + For information on the UMAP algorithm, see the `UMAP documentation `_. + Args: - data (Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. + data (typing.Any): The input data to be embedded. The data should be a NumPy array of shape `(number_of_samples, number_of_features)`. Returns: - Any: Returns the UMAP embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_new_features)`. - - Note: - For information on the UMAP algorithm, see the `UMAP documentation `_. + typing.Any: Returns the UMAP embedding of the input data as a NumPy array of shape `(number_of_samples, number_of_new_features)`. """ umap = UMAP( diff --git a/source/corelay/processor/flow.py b/source/corelay/processor/flow.py index 3f7a4b5..dbebf1b 100644 --- a/source/corelay/processor/flow.py +++ b/source/corelay/processor/flow.py @@ -1,68 +1,67 @@ -"""A module that contains basic flow operation processors, such as ``Shaper``, ``Sequential`` and ``Parallel``.""" +"""A module that contains basic flow operation processors, such as :py:class:`~corelay.processor.flow.Shaper`, +:py:class:`~corelay.processor.flow.Sequential` and :py:class:`~corelay.processor.flow.Parallel`. +""" +import typing from collections.abc import Iterable, Iterator, Sequence -from typing import Annotated, Any +from typing import Annotated from corelay.base import Param from corelay.processor.base import Processor from corelay.utils import zip_equal -RecursiveIndicesTuple = tuple['int | RecursiveIndicesTuple', ...] -"""A recursive tuple of integer indices, i.e., a tuple that contains integer indices or other tuples of integer indices, which themselves can contain -other tuples of integer indices, and so on. This is used to represent a nested structure of integer indices. -""" - - class Shaper(Processor): """Extracts and/or copies by indices. Args: - is_output (bool, optional): A value indicating whether this ``Shaper`` processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - indices (RecursiveIndicesTuple): The indices to copy/extract. The resulting output will be a tuple with the same member shape. Each index may - be passed an arbitrary amount of times. Outer tuples allow integers and tuples, inner tuples only allow integers. + is_output (bool): A value indicating whether this :py:class:`Shaper` processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + indices (tuple[int | tuple[int], ...]): The indices to copy/extract. The resulting output will be a tuple with the same member shape. Each + index may be passed an arbitrary amount of times. Outer tuples allow integers and tuples, inner tuples only allow integers. Examples: >>> Shaper(indices=(0, 1, (0, 1, 2)))(['a', 'b', 'c']) ('a', 'b', ('a', 'b', 'c')) """ - indices: Annotated[RecursiveIndicesTuple, Param(tuple, positional=True)] + indices: Annotated[tuple[int | tuple[int], ...], Param(tuple, positional=True)] """The indices to copy/extract. The resulting output will be a tuple with the same member shape. Each index may be passed an arbitrary amount of times. Outer tuples allow integers and tuples, inner tuples only allow integers. """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Extracts and/or copies indices of data. Args: - data (Any): The data from which the elements, identified by the indices, are to be extracted. This can be any object, but if it is not an - iterable, index 0 corresponds to the object itself. + data (typing.Any): The data from which the elements, identified by the indices, are to be extracted. This can be any object, but if it is + not an iterable, index 0 corresponds to the object itself. Raises: TypeError: An invalid index was accessed in the data. Returns: - Any: Returns the extracted/copied elements of the data, identified by the indices. The output is a tuple with the same member shape as the - indices. + typing.Any: Returns the extracted/copied elements of the data, identified by the indices. The output is a tuple with the same member shape + as the indices. """ - def extract_elements_from_indices(iterable: Sequence[Any], indices: RecursiveIndicesTuple) -> tuple[Any, ...]: + def extract_elements_from_indices(iterable: Sequence[typing.Any], indices: tuple[int | tuple[int], ...]) -> tuple[typing.Any, ...]: """Recursively extracts the elements of the specified ``iterable`` based on the specified ``indices``. Args: - iterable (Sequence[Any]): The iterable from which to extract the elements. - indices (RecursiveIndicesTuple): The indices to extract. This can be a tuple of integers or other tuples of integers. + iterable (Sequence[typing.Any]): The iterable from which to extract the elements. + indices (tuple[int | tuple[int], ...]): The indices to extract. This can be a tuple of integers or other tuples of integers. Raises: TypeError: An invalid index was accessed in the iterable. Returns: - tuple[Any, ...]: The extracted elements as a tuple. The output is a tuple with the same member shape as the indices. + tuple[typing.Any, ...]: Returns the extracted elements as a tuple. The output is a tuple with the same member shape as the indices. """ results = [] @@ -89,11 +88,13 @@ class GroupProcessor(Processor): """The abstract base class for groups of processors. Args: - is_output (bool, optional): A value indicating whether this ``GroupProcessor`` processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`GroupProcessor` processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. children (Iterable[Processor]): The children of the group. This is a list of processors that will be called in parallel or sequentially. """ @@ -106,19 +107,21 @@ class Parallel(GroupProcessor): Note: Please note, that the child processors are not executed in parallel in the sense of multiprocessing, but that the children all either receive - the same input data or an element of the input data, in contrast to the ``Sequential`` processor group, which first executes the first child - and then feeds the output to the next child. + the same input data or an element of the input data, in contrast to the :py:class:`Sequential` processor group, which first executes the first + child and then feeds the output to the next child. Args: - is_output (bool, optional): A value indicating whether this ``Parallel`` group processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`Parallel` group processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. children (Iterable[Processor]): The children of the group. This is a list of processors that will be called in parallel. - broadcast (bool, optional): A value indicating whether the input data should be broadcasted to all children. If ``True``, the input data will - be copied as many times as there are children. If ``False`` and the input is an iterable, the elements of the iterable will be passed to - the children one by one. Defaults to `False`. + broadcast (bool): A value indicating whether the input data should be broadcasted to all children. If :py:obj:`True`, the input data will + be copied as many times as there are children. If :py:obj:`False` and the input is an iterable, the elements of the iterable will be + passed to the children one by one. Defaults to :py:obj:`False`. Examples: >>> Parallel(children=[FunctionProcessor(processing_function=lambda x: x**n) for n in (1, 2, 3, 4)])((2, 2, 2, 2)) @@ -128,36 +131,37 @@ class Parallel(GroupProcessor): """ broadcast: Annotated[bool, Param(bool, False)] - """A value indicating whether the input data should be broadcasted to all children. If ``True``, the input data will be copied as many times as - there are children. If ``False`` and the input is an iterable, the elements of the iterable will be passed to the children one by one. Defaults to - `False`. + """A value indicating whether the input data should be broadcasted to all children. If :py:obj:`True`, the input data will be copied as many times + as there are children. If :py:obj:`False` and the input is an iterable, the elements of the iterable will be passed to the children one by one. + Defaults to :py:obj:`False`. """ - def function(self, data: Any) -> Any: - """Invokes the children in parallel, passing the input data to each child. If ``broadcast`` is ``True``, the input data will be copied as many - times as there are children. If ``broadcast`` is ``False`` and the input is an iterable, the elements of the iterable will be passed to the - children one by one. + def function(self, data: typing.Any) -> typing.Any: + """Invokes the children in parallel, passing the input data to each child. If :py:attr:`broadcast` is :py:obj:`True`, the input data will be + copied as many times as there are children. If :py:attr:`broadcast` is :py:obj:`False` and the input is an iterable, the elements of the + iterable will be passed to the children one by one. Args: - data (Any): The input data to pass to the children. If ``broadcast`` is ``True``, this can be any object. If ``broadcast`` is ``False``, - and the input is an iterable, the elements of the iterable will be passed to the children one by one. + data (typing.Any): The input data to pass to the children. If :py:attr:`broadcast` is :py:obj:`True`, this can be any object. If + :py:class:`broadcast` is :py:obj:`False`, and the input is an iterable, the elements of the iterable will be passed to the children + one by one. Raises: - TypeError: The ``broadcast`` parameter is set to `True`, and the number of children and number of data elements mismatch. + TypeError: The :py:attr:`broadcast` parameter is set to :py:obj:`True`, and the number of children and number of data elements mismatch. Returns: - Any: Returns a tuple that has the same number of elements as there are children and contains the outputs of the child processors. + typing.Any: Returns a tuple that has the same number of elements as there are children and contains the outputs of the child processors. """ - def wrap_iterator_with_meaningful_exception(iterable: Iterable[Any]) -> Iterator[Any]: + def wrap_iterator_with_meaningful_exception(iterable: Iterable[typing.Any]) -> Iterator[typing.Any]: """An iterator that wraps the passed iterator and raises an error, if the number of elements that the iterator produces is not the same as the number of child processors. This is done so that the error message is more informative. Args: - iterable (Iterable[Any]): The iterator to wrap. + iterable (Iterable[typing.Any]): The iterator to wrap. Yields: - Any: The elements of the iterator. + typing.Any: The elements of the iterator. Raises: TypeError: The number of elements that the iterator produces is not the same as the number of child processors. @@ -189,12 +193,13 @@ class Sequential(GroupProcessor): """A processor group that invokes its children in sequence, feeding the input the first child, and then each output to the next child. Args: - is_output (bool, optional): A value indicating whether this ``Sequential`` group processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`Sequential` group processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. children (Iterable[Processor]): The children of the group. This is a list of processors that will be called in sequentially. Examples: @@ -202,15 +207,15 @@ class Sequential(GroupProcessor): 'dcba=' """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Invokes the child processors of the processor group in sequence. The input data is fed to the first child, whose output is then fed into the second child, and so on. Args: - data (Any): The input data to pass to the first child. + data (typing.Any): The input data to pass to the first child. Returns: - Any: Returns the output of the last child processor. + typing.Any: Returns the output of the last child processor. """ for child in self.children: diff --git a/source/corelay/processor/laplacian.py b/source/corelay/processor/laplacian.py index 153315f..25a8772 100644 --- a/source/corelay/processor/laplacian.py +++ b/source/corelay/processor/laplacian.py @@ -1,23 +1,22 @@ """A module that contains processors for computing the graph Laplacian, which is mainly used for spectral embeddings.""" -from typing import Any +import typing import numpy import scipy.sparse -from numpy.typing import NDArray from corelay.processor.base import Processor -def a1ifmat(matrix: NDArray[Any] | numpy.matrix[Any, Any]) -> NDArray[Any]: +def a1ifmat(matrix: numpy.ndarray[typing.Any, typing.Any] | numpy.matrix[typing.Any, typing.Any]) -> numpy.ndarray[typing.Any, typing.Any]: """Converts the specified matrix to a flat representation. If the matrix is already a flat NumPy array, it is returned as is. Args: - matrix (NDArray[Any] | numpy.matrix[Any, Any]): The input matrix to be converted. + matrix (numpy.ndarray[typing.Any, typing.Any] | numpy.matrix[typing.Any, typing.Any]): The input matrix to be converted. Returns: - NDArray[Any]: The converted matrix. If the input was a NumPy matrix, it is returned as a flat NumPy array. Otherwise, the input is returned as - is. + numpy.ndarray[typing.Any, typing.Any]: Returns the converted matrix. If the input was a NumPy matrix, it is returned as a flat NumPy array. + Otherwise, the input is returned as is. """ return matrix.A1 if isinstance(matrix, numpy.matrix) else matrix @@ -27,61 +26,65 @@ class Laplacian(Processor): """The abstract base class for processors that compute a graph Laplacian. Args: - is_output (bool, optional): A value indicating whether this ``Laplacian`` processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`Laplacian` processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. """ class SymmetricNormalLaplacian(Laplacian): - """A ``Processor`` that computes the normal symmetric graph Laplacian. + """A :py:class:`~corelay.processor.base.Processor` that computes the normal symmetric graph Laplacian. Args: - is_output (bool, optional): A value indicating whether this ``SymmetricNormalLaplacian`` Laplacian processor is the output of a ``Pipeline``. - Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`SymmetricNormalLaplacian` Laplacian processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the symmetric normal graph Laplacian. Args: - data (Any): The graph affinity/similarity matrix. This can be a NumPy array or a sparse matrix. + data (typing.Any): The graph affinity/similarity matrix. This can be a NumPy array or a sparse matrix. Returns: - Any: Returns the symmetric normal graph Laplacian, which is a sparse representation of the symmetric graph Laplacian matrix. + typing.Any: Returns the symmetric normal graph Laplacian, which is a sparse representation of the symmetric graph Laplacian matrix. """ - input_data: scipy.sparse.csr_matrix | NDArray[Any] = data + input_data: scipy.sparse.csr_matrix | numpy.ndarray[typing.Any, typing.Any] = data degree = scipy.sparse.diags(a1ifmat(input_data.sum(1))**-0.5, 0) return degree @ input_data @ degree class RandomWalkNormalLaplacian(Laplacian): - """A ``Processor`` that computes the normal random walk graph Laplacian. + """A :py:class:`~corelay.processor.base.Processor` that computes the normal random walk graph Laplacian. Args: - is_output (bool, optional): A value indicating whether this ``RandomWalkNormalLaplacian`` Laplacian processor is the output of a ``Pipeline``. - Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. + is_output (bool): A value indicating whether this :py:class:`RandomWalkNormalLaplacian` Laplacian processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there + exists a previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. """ - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes the random walk normal graph Laplacian. Args: - data (Any): The graph affinity/similarity matrix. This can be a NumPy array or a sparse matrix. + data (typing.Any): The graph affinity/similarity matrix. This can be a NumPy array or a sparse matrix. Returns: - Any: Returns the random walk normal graph Laplacian, which is a sparse representation of the random walk graph Laplacian matrix. + typing.Any: Returns the random walk normal graph Laplacian, which is a sparse representation of the random walk graph Laplacian matrix. """ degree = scipy.sparse.diags(a1ifmat(data.sum(1))**-1., 0) diff --git a/source/corelay/processor/preprocessing.py b/source/corelay/processor/preprocessing.py index 354d506..445132e 100644 --- a/source/corelay/processor/preprocessing.py +++ b/source/corelay/processor/preprocessing.py @@ -1,12 +1,12 @@ -"""A module that contains processors for pre-processing data.""" +"""A module that contains processors for pre-processing data and images.""" +import typing from types import FunctionType -from typing import Annotated, Any +from typing import Annotated import numpy import skimage.measure import skimage.transform -from numpy.typing import NDArray from corelay.base import Param from corelay.processor.base import Processor @@ -16,51 +16,55 @@ class PreProcessor(Processor): """The abstract base class for pre-processing processors. Args: - is_output (bool, optional): A value indicating whether this ``PreProcessor`` is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict, optional): Additional keyword arguments to pass to the pre-processing function. Defaults to an empty dictionary. + is_output (bool): A value indicating whether this :py:class:`PreProcessor` is the output of a :py:class:`~corelay.pipeline.base.Pipeline`. + Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict): Additional keyword arguments to pass to the pre-processing function. Defaults to an empty :py:class:`dict`. """ - kwargs: Annotated[dict[str, Any], Param(dict, {})] + kwargs: Annotated[dict[str, typing.Any], Param(dict, {})] """Additional keyword arguments to pass to the pre-processing function.""" class Histogram(PreProcessor): - """A ``Processor`` that computes channel-wise histograms of the input data. + """A :py:class:`~corelay.processor.base.Processor` that computes channel-wise histograms of the input data. Args: - is_output (bool, optional): A value indicating whether this ``Histogram`` pre-processor is the output of a ``Pipeline``. Defaults to `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict, optional): Additional keyword arguments to pass to the pre-processing function. Defaults to an empty dictionary. - bins (int, optional): The number of bins for the histogram. Defaults to 32. + is_output (bool): A value indicating whether this :py:class:`Histogram` pre-processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict): Additional keyword arguments to pass to the pre-processing function. Defaults to an empty :py:class:`dict`. + bins (int): The number of bins for the histogram. Defaults to 32. """ bins: Annotated[int, Param(int, 32)] """Number of bins for the histogram. Defaults to 32.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Computes channel-wise histograms from the input data. Args: - data (Any): The input data to compute histograms for, which is should be an image as a NumPy array of shape + data (typing.Any): The input data to compute histograms for, which is should be an image as a NumPy array of shape `(number_of_samples, number_of_channels, height, width)`. Returns: - Any: Returns a NumPy array, which contains the channel-wise histograms of the input data of shape - `(number_of_samples, number_of_channels, bins)`. + typing.Any: Returns a NumPy array, which contains the channel-wise histograms of the input data of shape + `(number_of_samples, number_of_channels, bins)`. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data number_of_samples, number_of_channels, height, width = input_data.shape - channel_minima: NDArray[numpy.float64] = input_data.min((0, 2, 3)) - channel_maxima: NDArray[numpy.float64] = input_data.max((0, 2, 3)) + channel_minima: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = input_data.min((0, 2, 3)) + channel_maxima: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = input_data.max((0, 2, 3)) channel_range = list(zip(channel_minima, channel_maxima)) histogram, _ = numpy.histogramdd( @@ -76,15 +80,16 @@ class ImagePreProcessor(PreProcessor): """The abstract base class for all processors that perform pre-processing on images. Args: - is_output (bool, optional): A value indicating whether this ``ImagePreProcessor`` pre-processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. - filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). - channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. + is_output (bool): A value indicating whether this :py:class:`ImagePreProcessor` pre-processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty :py:class:`dict`. + filter (int): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool): A value indicating whether the input data is in channels-first format or not. Defaults to :py:obj:`True`. """ filter: Annotated[int, Param(int, 1)] @@ -101,24 +106,25 @@ class ImagePreProcessor(PreProcessor): """ channels_first: Annotated[bool, Param(bool, True)] - """A value indicating whether the input data is in channels-first format or not. Defaults to `True`.""" + """A value indicating whether the input data is in channels-first format or not. Defaults to :py:obj:`True`.""" class Resize(ImagePreProcessor): - """A ``Processor`` that resizes images to a specified width and height. + """A :py:class:`~corelay.processor.base.Processor` that resizes images to a specified width and height. Args: - is_output (bool, optional): A value indicating whether this ``Resize`` image pre-processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. - filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). - channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. - width (int, optional): The width to which the images are resized. Defaults to 100. - height (int, optional): The height to which the images are resized. Defaults to 100. + is_output (bool): A value indicating whether this :py:class:`Resize` image pre-processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty :py:class:`dict`. + filter (int): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool): A value indicating whether the input data is in channels-first format or not. Defaults to :py:obj:`True`. + width (int): The width to which the images are resized. Defaults to 100. + height (int): The height to which the images are resized. Defaults to 100. """ width: Annotated[int, Param(int, 100)] @@ -127,22 +133,22 @@ class Resize(ImagePreProcessor): height: Annotated[int, Param(int, 100)] """The height to which the images are resized. Defaults to 100.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Resizes the input images to the specified width and height. Args: - data (Any): The input data, which contains the images that are to be resized. The input data should be a NumPy array in one of the + data (typing.Any): The input data, which contains the images that are to be resized. The input data should be a NumPy array in one of the following formats: - 1. `(batch_size, number_of_channels, height, width)`, if ``channels_first`` is set to `True`. - 2. `(batch_size, height, width, number_of_channels)`, if ``channels_first`` is set to `False`. - 3. `(batch_size, height, width)`, if ``channels_first`` is set to `False`. + 1. `(batch_size, number_of_channels, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`True`. + 2. `(batch_size, height, width, number_of_channels)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. + 3. `(batch_size, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. Returns: - Any: Returns a NumPy array containing the resized images, with a shape that matches the input data format. + typing.Any: Returns a NumPy array containing the resized images, with a shape that matches the input data format. """ - input_images: NDArray[Any] = data + input_images: numpy.ndarray[typing.Any, typing.Any] = data if self.channels_first: input_images = numpy.moveaxis(data, 1, -1) @@ -162,40 +168,41 @@ def function(self, data: Any) -> Any: class Rescale(ImagePreProcessor): - """A ``Processors`` that rescales images by a specified scale factor. + """A :py:class:`~corelay.processor.base.Processor` that rescales images by a specified scale factor. Args: - is_output (bool, optional): A value indicating whether this ``Rescale`` image pre-processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. - filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). - channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. - scale (float, optional): The scale factor by which the images are rescaled. Defaults to 0.5. + is_output (bool): A value indicating whether this :py:class:`Rescale` image pre-processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty :py:class:`dict`. + filter (int): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool): A value indicating whether the input data is in channels-first format or not. Defaults to :py:obj:`True`. + scale (float): The scale factor by which the images are rescaled. Defaults to 0.5. """ scale: Annotated[float, Param(float, 0.5)] """The scale factor by which the images are rescaled. Defaults to 0.5.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Rescales the input images by the specified scale factor. Args: - data (Any): The input data, which contains the images that are to be rescaled. The input data should be a NumPy array in one of the + data (typing.Any): The input data, which contains the images that are to be rescaled. The input data should be a NumPy array in one of the following formats: - 1. `(batch_size, number_of_channels, height, width)`, if ``channels_first`` is set to `True`. - 2. `(batch_size, height, width, number_of_channels)`, if ``channels_first`` is set to `False`. - 3. `(batch_size, height, width) `, if ``channels_first`` is set to `False`. + 1. `(batch_size, number_of_channels, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`True`. + 2. `(batch_size, height, width, number_of_channels)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. + 3. `(batch_size, height, width) `, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. Returns: - Any: Returns a NumPy array containing the rescaled images, with a shape that matches the input data format. + typing.Any: Returns a NumPy array containing the rescaled images, with a shape that matches the input data format. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data images_are_multi_channel = len(input_data.shape) > 3 if self.channels_first: @@ -217,40 +224,41 @@ def function(self, data: Any) -> Any: class Pooling(PreProcessor): - """A ``Processor`` that performs image pooling on the input data. + """A :py:class:`~corelay.processor.base.Processor` that performs image pooling on the input data. Args: - is_output (bool, optional): A value indicating whether this ``Pooling`` image pre-processor is the output of a ``Pipeline``. Defaults to - `False`. - is_checkpoint (bool | None, optional): A value indicating whether check-pointed pipeline computations should start at this point, if there - exists a previously computed checkpoint value. Defaults to `False`. - io (Storable | None, optional): An IO object that is used to cache intermediate results of the pipeline, which can then be re-used in this - run or in subsequent runs of the ``Pipeline``. Defaults to an instance of ``NoStorage``. - kwargs (dict, optional): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty dictionary. - filter (int, optional): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). - channels_first (bool, optional): A value indicating whether the input data is in channels-first format or not. Defaults to `True`. - stride (tuple[int], optional): The pooling stride, which should be of shape `(batch_size, number_of_channels, height, width)`. Defaults to + is_output (bool): A value indicating whether this :py:class:`Pooling` image pre-processor is the output of a + :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to :py:obj:`False`. + is_checkpoint (bool | None): A value indicating whether check-pointed pipeline computations should start at this point, if there exists a + previously computed checkpoint value. Defaults to :py:obj:`False`. + io (Storable | None): An IO object that is used to cache intermediate results of the :py:class:`~corelay.pipeline.base.Pipeline`, which can + then be re-used in this run or in subsequent runs of the :py:class:`~corelay.pipeline.base.Pipeline`. Defaults to an instance of + :py:class:`~corelay.io.NoStorage`. + kwargs (dict): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty :py:class:`dict`. + filter (int): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). + channels_first (bool): A value indicating whether the input data is in channels-first format or not. Defaults to :py:obj:`True`. + stride (tuple[int]): The pooling stride, which should be of shape `(batch_size, number_of_channels, height, width)`. Defaults to `(1, 1, 2, 2)`. - pooling_function (FunctionType, optional): The pooling function to use to reduce the selected blocks. Defaults to ``numpy.sum``. + pooling_function (FunctionType): The pooling function to use to reduce the selected blocks. Defaults to :py:func:`numpy.sum`. """ stride: Annotated[tuple[int], Param(tuple, (1, 1, 2, 2))] """The pooling stride, which should be of shape `(batch_size, number_of_channels, height, width)`. Defaults to `(1, 1, 2, 2)`.""" pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)] - """The pooling function to use to reduce the selected blocks. Defaults to ``numpy.sum``.""" + """The pooling function to use to reduce the selected blocks. Defaults to :py:func:`numpy.sum`.""" - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Performs pooling on the input data. Args: - data (Any): The input data, which should be a NumPy array of shape `(number_of_samples, number_of_channels, height, width)`. + data (typing.Any): The input data, which should be a NumPy array of shape `(number_of_samples, number_of_channels, height, width)`. Returns: - Any: Returns a NumPy array containing the pooled data. + typing.Any: Returns a NumPy array containing the pooled data. """ - input_data: NDArray[Any] = data + input_data: numpy.ndarray[typing.Any, typing.Any] = data return skimage.measure.block_reduce( # type: ignore[no-untyped-call] input_data, self.stride, diff --git a/source/corelay/tracker.py b/source/corelay/tracker.py index b5be8f6..a08d458 100644 --- a/source/corelay/tracker.py +++ b/source/corelay/tracker.py @@ -1,8 +1,10 @@ -"""Includes MetaTracker to track definition order of class attributes.""" +"""A module that contains the :py:class:`~corelay.tracker.Tracker`, which is used to track :py:class:`~corelay.plugboard.Slot` definitions in classes +that inherit from :py:class:`~corelay.plugboard.Plugboard`. +""" +import collections +import typing from abc import ABCMeta -from collections import OrderedDict -from typing import Any class MetaTracker(ABCMeta): @@ -10,32 +12,37 @@ class MetaTracker(ABCMeta): Note: This is used to track the slots of a class. In CoRelAy, slots are declared as class attributes, and during instantiation, the class attributes - are converted to respective instance attributes of the data type declared in the slot. For example, a ``Param`` slot is used to declare - parameters of processors. When a processor has a class attribute that is a ``Param`` with a data type of ``int``, then when the processor is - instantiated, an instance attribute of the same name is created with the data type of ``int``. There are two ways a slot can be declared: + are converted to respective instance attributes of the data type declared in the slot. For example, a :py:class:`~corelay.base.Param` slot is + used to declare parameters of processors. When a processor has a class attribute that is a :py:class:`~corelay.base.Param` with a data type of + :py:class:`int`, then when the processor is instantiated, an instance attribute of the same name is created with the data type of + :py:class:`int`. - 1) The old way of declaring a slot, which is to declare it as a class attribute and assign it a ``Slot`` instance, e.g., - ``param = Param(int, 0)``. This is not the recommended way of declaring a slot anymore, because it causes problems with static type + There are two ways a slot can be declared: + + 1) The old way of declaring a slot, which is to declare it as a class attribute and assign it a :py:class:`~corelay.plugboard.Slot` instance, + e.g., `param = Param(int, 0)`. This is not the recommended way of declaring a slot anymore, because it causes problems with static type checkers, like MyPy. In Python, class attributes can be accessed using the class or using the instance. For example, if a class ``Test`` - has a class attribute ``a = 5``, then ``Test.a`` and ``Test().a`` will both return ``5``. Static type checkers, like MyPy, do not know that - we are converting the class attribute ``a`` to an instance attribute of type ``int`` during instantiation, so they will assume that when a + has a class attribute `a = 5`, then `Test.a` and `Test().a` will both return `5`. Static type checkers, like MyPy, do not know that we are + converting the class attribute ``a`` to an instance attribute of type :py:class:`int` during instantiation, so they will assume that when a slot is accessed using the instance, it will have the same type as the class attribute. This means that the static type checker will show - an error when the slot is accessed using the instance, because the type of the class attribute is ``Slot`` and not ``int``. - 2) The new way of declaring a slot, which is to declare it as a class attribute of type ``Annotated``. The ``Annotated`` type is a special - type that allows us to add metadata to a type hint. The first argument of ``Annotated`` is the actual type of the attribute, and then any - number of additional arguments can be passed, which are used as metadata. So, for example, a parameter slot can be declared as - ``param: Annotated[float, Param(float, 0.0)]``. Since the static type checker knows that the actual type of the attribute is ``float``, it - will not show an error when the slot is accessed using the instance. The metadata is used to store the instance of the slot, which contains - the information about the data type, the default value, etc. Unfortunately, this is only a declaration, which has no effect on the runtime. - This means, that no actual class attribute is created. But ``Annotated`` will add a special ``__annotations__`` attribute to the class, - which contains a dictionary with the names of the declared class attributes and their metadata. - - The ``MetaTracker`` meta class is used to track the class attributes of a class that are not special "dunder" attributes like ``__class__``, - as well as the declared class attributes from the ``__annotations__`` attribute. The tracked class attributes are stored in an ``OrderedDict`` - called ``__tracked__``. The ``Tracker`` class uses the ``MetaTracker`` meta class and allows users to access the tracked class attributes. The - class attributes are tracked in the order they were declared in the class, with the caveat that first, all class attributes come in the order - of declaration, and then all declared class attributes come in the order of declaration. As long as only one method of declaring a slot is - used, the order of declaration will be preserved. + an error when the slot is accessed using the instance, because the type of the class attribute is :py:class:`~corelay.plugboard.Slot` and + not :py:class:`int`. + 2) The new way of declaring a slot, which is to declare it as a class attribute of type :py:class:`typing.Annotated`. The + :py:class:`typing.Annotated` type is a special type that allows us to add metadata to a type hint. The first argument of + :py:class:`typing.Annotated` is the actual type of the attribute, and then any number of additional arguments can be passed, which are used + as metadata. So, for example, a parameter slot can be declared as `param: Annotated[float, Param(float, 0.0)]`. Since the static type + checker knows that the actual type of the attribute is :py:class:`float`, it will not show an error when the slot is accessed using the + instance. The metadata is used to store the instance of the slot, which contains the information about the data type, the default value, + etc. Unfortunately, this is only a declaration, which has no effect on the runtime. This means, that no actual class attribute is created. + But :py:class:`typing.Annotated` will add a special ``__annotations__`` attribute to the class, which contains a :py:class:`dict` with the + names of the declared class attributes and their metadata. + + The :py:class:`MetaTracker` meta class is used to track the class attributes of a class that are not special "dunder" attributes like + ``__class__``, as well as the declared class attributes from the ``__annotations__`` attribute. The tracked class attributes are stored in an + :py:class:`collections.OrderedDict` called :py:attr:`Tracker.__tracked__`. The :py:class:`Tracker` class uses the :py:class:`MetaTracker` meta + class and allows users to access the tracked class attributes. The class attributes are tracked in the order they were declared in the class, + with the caveat that first, all class attributes come in the order of declaration, and then all declared class attributes come in the order of + declaration. As long as only one method of declaring a slot is used, the order of declaration will be preserved. For more information on meta classes, please refer to `PEP 3115 `_. @@ -45,7 +52,7 @@ class attributes are tracked in the order they were declared in the class, with ... b = 21 ... c = 42 ... OrderedInts(a=0).__tracked__ - OrderedDict([('a', 0), ('b', 21), ('c', 42)]) + collections.OrderedDict([('a', 0), ('b', 21), ('c', 42)]) """ @classmethod @@ -54,46 +61,48 @@ def __prepare__( # pylint: disable=unused-argument class_name: str, base_classes: tuple[type, ...], /, - **kwargs: Any - ) -> OrderedDict[str, Any]: - """Prepare the class dict to be an ``OrderedDict``. This is done to preserve the order of declaration of the class attributes. + **kwargs: typing.Any + ) -> collections.OrderedDict[str, typing.Any]: + """Prepare the class dict to be an :py:class:`collections.OrderedDict`. This is done to preserve the order of declaration of the class + attributes. Args: class_name (str): The name of the class. base_classes (tuple[type, ...]): The base classes of the class. - **kwargs (Any): Additional keyword arguments. + **kwargs (typing.Any): Additional keyword arguments. Returns: - OrderedDict[str, Any]: Returns a ``OrderedDict``, which will be used as the dictionary for the class attributes. + collections.OrderedDict[str, typing.Any]: Returns a :py:class:`collections.OrderedDict`, which will be used as the dictionary for the + class attributes. """ - return OrderedDict() + return collections.OrderedDict() - def __new__(mcs, class_name: str, base_classes: tuple[type, ...], class_attributes: OrderedDict[str, Any]) -> 'MetaTracker': - """Is called when a new class is created with the ``MetaTracker`` as its metaclass. Attaches a new ``__tracked__`` attribute to the class, - which is a dictionary with all public attributes of the class, i.e., those not enclosed in double underscores. If the class that is being - created already has a ``__tracked__`` attribute, the new attributes are appended to it. + def __new__(mcs, class_name: str, base_classes: tuple[type, ...], class_attributes: collections.OrderedDict[str, typing.Any]) -> 'MetaTracker': + """Is called when a new class is created with the :py:class:`MetaTracker` as its metaclass. Attaches a new :py:attr:`Tracker.__tracked__` + attribute to the class, which is a :py:class:`dict` with all public attributes of the class, i.e., those not enclosed in double underscores. + If the class that is being created already has a :py:attr:`Tracker.__tracked__` attribute, the new attributes are appended to it. Args: class_name (str): The name of the class. base_classes (tuple[type, ...]): The base classes of the class. - class_attributes (OrderedDict[str, Any]): A dictionary with the attributes of the class. In this case, with the addition of tracker - attributes. + class_attributes (collections.OrderedDict[str, typing.Any]): A :py:class:`dict` with the attributes of the class. In this case, with the + addition of tracker attributes. Returns: - MetaTracker: Returns the new class with the ``__tracked__`` attribute. + MetaTracker: Returns the new class with the :py:attr:`Tracker.__tracked__` attribute. """ # Retrieves the class attributes that are not special "dunder" attributes, like __class__, i.e., any class attributes that is not enclosed in # double underscores (this is the classical way in which slots can be declared) - tracked_class_attributes: OrderedDict[str, Any] = OrderedDict( + tracked_class_attributes: collections.OrderedDict[str, typing.Any] = collections.OrderedDict( (attribute_name, attribute_value) for attribute_name, attribute_value in class_attributes.items() if not (attribute_name[:2] + attribute_name[-2:]) == '____' ) # Retrieves the declared class attributes, which were declared using Annotated (this is the new way in which slots can be declared) - tracked_declared_class_attributes: OrderedDict[str, Any] = OrderedDict() + tracked_declared_class_attributes: collections.OrderedDict[str, typing.Any] = collections.OrderedDict() if '__annotations__' in class_attributes: for attribute_name, attribute_value in class_attributes['__annotations__'].items(): if (attribute_name[:2] + attribute_name[-2:]) == '____': @@ -111,12 +120,12 @@ def __new__(mcs, class_name: str, base_classes: tuple[type, ...], class_attribut class_attributes.update(tracked_declared_class_attributes) # Creates a new class with the given name, bases, and class attributes - new_class: Any = super().__new__(mcs, class_name, base_classes, dict(class_attributes)) + new_class: typing.Any = super().__new__(mcs, class_name, base_classes, dict(class_attributes)) # Checks if the class or one of its base classes already has a __tracked__ attribute, if not, a new __tracked__ attribute is created, # otherwise, the __tracked__ attribute is copied - tracked_attributes: OrderedDict[str, Any] = OrderedDict() - if hasattr(new_class, '__tracked__') and isinstance(new_class.__tracked__, OrderedDict): + tracked_attributes: collections.OrderedDict[str, typing.Any] = collections.OrderedDict() + if hasattr(new_class, '__tracked__') and isinstance(new_class.__tracked__, collections.OrderedDict): tracked_attributes = new_class.__tracked__.copy() # Adds the class attributes and the declared class attributes that were retrieved above @@ -130,33 +139,37 @@ def __new__(mcs, class_name: str, base_classes: tuple[type, ...], class_attribut class Tracker(metaclass=MetaTracker): - """Tracks all public class attributes, i.e., all class attributes not enclosed int double underscores. This makes them available in a class - attribute ``__tracked__`` using the meta class ``MetaTracker``. + """Tracks all public class attributes, i.e., all class attributes not enclosed in double underscores. This makes them available in a class + attribute :py:attr:`Tracker.__tracked__` using the meta class :py:class:`MetaTracker`. """ - __tracked__: OrderedDict[str, Any] - """An ``OrderedDict`` with all public class attributes, i.e., all class attributes not enclosed with double underscores.""" + __tracked__: collections.OrderedDict[str, typing.Any] + """An :py:class:`collections.OrderedDict` with all public class attributes, i.e., all class attributes not enclosed with double underscores. + + :meta hide-value: + """ @classmethod - def collect(cls, dtype: type | tuple[type, ...]) -> OrderedDict[str, Any]: + def collect(cls, dtype: type | tuple[type, ...]) -> collections.OrderedDict[str, typing.Any]: """Retrieves all tracked class attributes of a certain type. Args: dtype (type | tuple[type, ...]): The type or types of the class attributes to retrieve. Returns: - OrderedDict[str, Any]: Returns an ``OrderedDict`` that contains the public class attributes, i.e., all class attributes not enclosed in - double underscores, of the given type or types. The keys are the attribute names and the values are the attribute values. + collections.OrderedDict[str, typing.Any]: Returns an :py:class:`collections.OrderedDict` that contains the public class attributes, i.e., + all class attributes not enclosed in double underscores, of the given type or types. The keys are the attribute names and the values are + the attribute values. """ - return OrderedDict( + return collections.OrderedDict( (attribute_name, attribute_value) for attribute_name, attribute_value in cls.__tracked__.items() if isinstance(attribute_value, dtype) ) @classmethod - def get(cls, attribute_name: str) -> Any: + def get(cls, attribute_name: str) -> typing.Any: """Retrieves a tracked class attribute by name. Args: @@ -166,31 +179,33 @@ def get(cls, attribute_name: str) -> Any: AttributeError: The class attribute does not exist. Returns: - Any: Returns the value of the class attribute with the given name. If the class attribute does not exist `None` is returned. + typing.Any: Returns the value of the class attribute with the given name. If the class attribute does not exist :py:obj:`None` is + returned. """ if attribute_name not in cls.__tracked__: raise AttributeError(f"Class attribute '{attribute_name}' does not exist.") return cls.__tracked__.get(attribute_name) - def collect_attr(self, dtype: type | tuple[type, ...]) -> OrderedDict[str, Any]: + def collect_attr(self, dtype: type | tuple[type, ...]) -> collections.OrderedDict[str, typing.Any]: """Retrieves all instance attributes, corresponding to tracked class attributes of a certain type. Args: dtype (type | tuple[type, ...]): The type or types of the instance attributes to retrieve. Returns: - OrderedDict[str, Any]: Returns an ``OrderedDict`` that contains the instance attributes, corresponding to tracked class attributes, of the - given type or types. The keys are the attribute names and the values are the attribute values. + collections.OrderedDict[str, typing.Any]: Returns an :py:class:`collections.OrderedDict` that contains the instance attributes, + corresponding to tracked class attributes, of the given type or types. The keys are the attribute names and the values are the attribute + values. """ - return OrderedDict( + return collections.OrderedDict( (attribute_name, getattr(self, attribute_name, None)) for attribute_name, attribute_value in self.__tracked__.items() if isinstance(attribute_value, dtype) ) - def get_attr(self, attribute_name: str) -> Any: + def get_attr(self, attribute_name: str) -> typing.Any: """Retrieves an instance attribute, corresponding to a tracked class attribute, by name. Args: @@ -200,7 +215,8 @@ def get_attr(self, attribute_name: str) -> Any: AttributeError: The instance attribute does not exist. Returns: - Any: Returns the value of the instance attribute with the given name. If the instance attribute does not exist `None` is returned. + typing.Any: Returns the value of the instance attribute with the given name. If the instance attribute does not exist + :py:obj:`None` is returned. """ if attribute_name not in self.__tracked__: diff --git a/source/corelay/utils.py b/source/corelay/utils.py index 326ce06..a6cf6e9 100644 --- a/source/corelay/utils.py +++ b/source/corelay/utils.py @@ -1,24 +1,33 @@ -"""A module containing utility classes and functions for CoRelAy, such as an iterable with member type checking, an enhanced zip function, and -conditional import functions. +"""A module containing utility classes and functions for CoRelAy. This includes as an enhanced :py:func:`zip` function, which ensures that the +sequences being zipped are of equal length. Furthermore, this module contains functions for getting the representations of runtime objects like +functions, methods, classes, and lambda expressions and conditional import functions, which return dummy functions, classes, or modules that raise an +error when called or accessed, indicating how to install the missing dependencies for the functionality to work. """ +import ast +import inspect +import os +import types +import typing from collections.abc import Callable, Iterable, Iterator from importlib import import_module -from types import ModuleType -from typing import Any, NoReturn, overload +from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, LambdaType, MethodType, ModuleType +from typing import NoReturn, overload +import numpy -def zip_equal(*args: Iterable[Any]) -> Iterator[tuple[Any, ...]]: - """Zips its positional arguments, but only if they are of equal length. + +def zip_equal(*args: Iterable[typing.Any]) -> Iterator[tuple[typing.Any, ...]]: + """Zips the positional arguments, but only if they are of equal length. Args: - *args (Iterable[Any]): The iterables that are to be zipped. They must be of equal length. + *args (Iterable[typing.Any]): The iterables that are to be zipped. They must be of equal length. Raises: TypeError: The positional arguments have different lengths. Yields: - tuple[Any, ...]: Yields the zipped elements of the positional arguments. + tuple[typing.Any, ...]: Yields the zipped elements of the positional arguments. """ iterators = [iter(iterable) for iterable in args] @@ -43,31 +52,147 @@ def zip_equal(*args: Iterable[Any]) -> Iterator[tuple[Any, ...]]: yield tuple(zipped_element) -def dummy_from_module_import(module_name: str) -> Callable[..., Any]: +def get_lambda_expression_source_code(lambda_expression: LambdaType) -> str: + """Returns the source code of the specified lambda expression if possible. This is done by retrieving the source code of the lambda expression and + parsing it into an abstract syntax tree (AST). The AST node that represents the lambda expression is then used to retrieve the source code of the + lambda expression by repeatedly removing the trailing junk from the source code until the source code can be compiled without errors, is at least + as long as the shortest possible lambda expression, and the length of the compiled byte code is the same as the length as the byte code of the + original lambda expression. + + Note: + This function was adapted from the original implementation by Karol Kuczmarski, outline in their blog post + `Source code of a Python lambda `_. The code was published as a + `Gist on GitHub `_ and was published under the + `Creative Commons Attribution-ShareAlike 4.0 International License `_. The original code + was modified to fit the coding style of this project and to return "" instead of :py:obj:`None` if the source + code cannot be retrieved. + + Args: + lambda_expression (LambdaType): The lambda expression for which the source code is to be returned. + + Returns: + str: Returns the source code of the lambda expression if possible, otherwise "" is returned. + """ + + # Retrieves the line of the source code that contains the lambda expression, if the lambda expression is defined over multiple lines, then they + # are joined together with new line characters + try: + source_lines, _ = inspect.getsourcelines(lambda_expression) + except (IOError, TypeError): + return '' + if len(source_lines) != 1: + return '' + source_code = os.linesep.join(source_lines).strip() + + # Parses the source code into an AST and retrieves the AST node that represents the lambda expression + source_ast = ast.parse(source_code) + lambda_ast_node = next((node for node in ast.walk(source_ast) if isinstance(node, ast.Lambda)), None) + if lambda_ast_node is None: + return '' + + # Since we can (and most likely will) get source lines where the lambda expression is just a part of bigger expressions, they will have some + # trailing junk after their definition; unfortunately, AST nodes only keep their _starting_ offsets from the original source, so we have to + # determine the end ourselves; this is done by gradually shaving extra junk from after the definition while ensuring that the code is still valid, + # is at least as long as the shortest possible lambda expression, and that the length of the compiled byte code is the same as the length of the + # byte code of the original lambda expression + lambda_source_code = source_code[lambda_ast_node.col_offset:] + lambda_body_source_code = source_code[lambda_ast_node.body.col_offset:] + length_of_smallest_valid_lambda_expression = len('lambda:_') + while len(lambda_source_code) > length_of_smallest_valid_lambda_expression: + try: + + # What's annoying is that sometimes the junk even parses, but results in a different lambda; You'd probably have to be deliberately + # malicious to exploit it but here's one way: + # + # lambda_expression_tuple = lambda x: False, lambda x: True + # get_lambda_expression_source_code(lambda_expression_tuple[0]) + # + # Ideally, we'd just keep shaving until we get the same code, but that most likely won't happen because we can't replicate the exact + # closure environment; thus the next best thing is to assume some divergence due to, e.g., LOAD_GLOBAL in original code being LOAD_FAST in + # the one compiled above, or vice versa; But the resulting code should at least be the same length, if otherwise the same operations are + # performed in it + code = compile(lambda_body_source_code, '', 'eval') + if len(code.co_code) == len(lambda_expression.__code__.co_code): + return lambda_source_code + + # Syntax errors are expected, so we just ignore them; they are most likely caused by the trailing junk in the source code that has not, yet, + # been removed + except SyntaxError: + pass + + # Shaves off the last character of the source code of the lambda expression and the body of the lambda expression + lambda_source_code = lambda_source_code[:-1] + lambda_body_source_code = lambda_body_source_code[:-1] + + # If the source code cannot be retrieved, then "" is returned + return '' + + +def get_object_representation(obj: typing.Any) -> str: + """Returns a :py:class:`str` representation of the object, which depends on the type of the object. If the object is a type, module, function or + method, then its name is returned, if the object is a lambda expression, then its source code is returned, otherwise the :py:class:`str` + representation of the object is returned. + + Args: + obj (typing.Any): The object for which the :py:class:`str` representation is to be returned. + + Returns: + str: Returns a :py:class:`str` representation of the object. + """ + + if isinstance(obj, LambdaType): + return get_lambda_expression_source_code(obj) + + if isinstance(obj, (type, ModuleType, FunctionType, MethodType, BuiltinFunctionType, BuiltinMethodType, numpy.ufunc, type(numpy.max))): + return get_fully_qualified_name(obj) + + return repr(obj) + + +def get_fully_qualified_name(obj: typing.Any) -> str: + """Returns the fully qualified name of the object, which is the module name and the name of the object. + + Args: + obj (typing.Any): The object for which the fully qualified name is to be returned. + + Returns: + str: Returns the fully qualified name of the object. + """ + + object_class: type = obj.__class__ + object_module = object_class.__module__ + if object_module == 'builtins': + return object_class.__qualname__ + return object_module + '.' + object_class.__qualname__ + + +def dummy_from_module_import(module_name: str) -> Callable[..., typing.Any]: """Creates a stub function that raises an error when called. This is used to replace an actual import of a type or function from a module, like `from module import type` or `from module import function`, of a package that has not been installed. It is useful for optional dependencies of a package, because the retrieved function will raise an exception that tells the user how to install the missing dependencies for the functionality to work. It does not matter if the user meant to import a function or a type, as types are also callable (i.e., if a type is called, then an - instance of the type is created and the constructor ``__init__`` is called) and invoking a function is syntactically indistinguishable from - instantiating an instance of a type. If the user meant to import a function, then the exception will be raised when the function is called. If the - user meant to import a type, then the exception will be raised when the user tries to instantiate an instance of the type. + instance of the type is created and the constructor ``__init__`` of that type is called) and invoking a function is syntactically + indistinguishable from instantiating an instance of a type. If the user meant to import a function, then the exception will be raised when the + function is called. If the user meant to import a type, then the exception will be raised when the user tries to instantiate an instance of the + type. Args: module_name (str): The name of the module from which the type or function that is to be replaced was imported. Returns: - Callable[..., Any]: Returns a function that raises an error when called, which instructs the user on how to install the missing dependencies. + Callable[..., typing.Any]: Returns a function that raises an error when called, which instructs the user on how to install the missing + dependencies. """ - def function(*_args: Any, **_kwargs: Any) -> NoReturn: + def function(*_args: typing.Any, **_kwargs: typing.Any) -> NoReturn: """A stub function that raises an error when called, which instructs the user on how to install the missing dependencies. Args: - *_args (Any): The positional arguments that were passed to the function. - **_kwargs (Any): The keyword arguments that were passed to the function. + *_args (typing.Any): The positional arguments that were passed to the function. + **_kwargs (typing.Any): The keyword arguments that were passed to the function. Raises: - RuntimeError: Always raises a ``RuntimeError`` with a message indicating how to install the missing dependencies. + RuntimeError: Always raises a :py:class:`RuntimeError` with a message indicating how to install the missing dependencies. """ # Retrieves the name of this package, which is the first part of the fully-qualified name of this module; this should always be "corelay", @@ -82,7 +207,7 @@ def function(*_args: Any, **_kwargs: Any) -> NoReturn: return function -def dummy_import_module(module_name: str) -> Any: +def dummy_import_module(module_name: str) -> typing.Any: """Creates a stub class that raises an error when any of its attributes are accessed and returns an instance of it. This is used to replace an actual import of a module, like `import module`, of a package that has not been installed. It is useful for optional dependencies of a package, because the retrieved object will raise an exception that tells the user how to install the missing dependencies for the functionality to work. It @@ -94,8 +219,8 @@ def dummy_import_module(module_name: str) -> Any: module_name (str): The name of the module that is to be replaced. Returns: - Any: Returns an instance of a class that raises an error when any of its attributes are accessed, which instructs the user on how to install - the missing dependencies. + typing.Any: Returns an instance of a class that raises an error when any of its attributes are accessed, which instructs the user on how to + install the missing dependencies. """ class Class: @@ -104,14 +229,14 @@ class Class: """ def __getattr__(self, _item: str) -> NoReturn: - """Is invoked when an attribute of the class is accessed. It raises a ``RuntimeError`` with a message indicating how to install the - missing dependencies. + """Is invoked when an attribute of the class is accessed. It raises a :py:class:`RuntimeError` with a message indicating how to install + the missing dependencies. Args: _item (str): The name of the attribute that was accessed. Raises: - RuntimeError: Always raises a ``RuntimeError`` with a message indicating how to install the missing dependencies. + RuntimeError: Always raises a :py:class:`RuntimeError` with a message indicating how to install the missing dependencies. """ # Retrieves the name of this package, which is the first part of the fully-qualified name of this module; this should always be "corelay", @@ -127,7 +252,7 @@ def __getattr__(self, _item: str) -> NoReturn: @overload -def import_or_stub(module_name: str) -> ModuleType: +def import_or_stub(module_name: str) -> types.ModuleType: """Tries to import a module. If the import fails, the requested module is replaced with a dummy that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved module will raise an exception that tells the user how to install the missing dependencies for the functionality to work. @@ -136,13 +261,13 @@ def import_or_stub(module_name: str) -> ModuleType: module_name (str): The name of the module that is to be imported. Returns: - ModuleType: Returns the imported module. If the import of the module fails, a dummy is returned that will raise an exception when used, - telling the user how to install the missing dependencies. + types.ModuleType: Returns the imported module. If the import of the module fails, a dummy is returned that will raise an exception when used, + telling the user how to install the missing dependencies. """ @overload -def import_or_stub(module_name: str, type_and_function_names: str) -> Callable[..., Any]: +def import_or_stub(module_name: str, type_and_function_names: str) -> Callable[..., typing.Any]: """Tries to import a type or a function from a module. If the import fails, the requested type or function is replaced with a dummy that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved type or function will raise an exception that tells the user how to install the missing dependencies for the functionality to work. @@ -152,13 +277,13 @@ def import_or_stub(module_name: str, type_and_function_names: str) -> Callable[. type_and_function_names (str): The name of the type or function that is to be imported from the module. Returns: - Callable[..., Any]: Returns the imported type or function. If the import of the module fails, a dummy is returned that will raise an - exception when used, telling the user how to install the missing dependencies. + Callable[..., typing.Any]: Returns the imported type or function. If the import of the module fails, a dummy is returned that will raise an + exception when used, telling the user how to install the missing dependencies. """ @overload -def import_or_stub(module_name: str, type_and_function_names: tuple[str, ...]) -> list[ModuleType | Callable[..., Any]]: +def import_or_stub(module_name: str, type_and_function_names: tuple[str, ...]) -> list[types.ModuleType | Callable[..., typing.Any]]: """Tries to import types and/or functions from a module. If the import fails, the requested types and/or functions are replaced with dummies that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved types and/or functions will raise an exception that tells the user how to install the missing dependencies for the functionality to work. @@ -168,38 +293,39 @@ def import_or_stub(module_name: str, type_and_function_names: tuple[str, ...]) - type_and_function_names (tuple[str, ...]): The names of the types and/or functions that are to be imported from the module. Returns: - list[ModuleType | Callable[..., Any]]: Returns a list of the imported modules, types and/or functions. If the import of the module fails, - dummies are returned that will raise an exception when used, telling the user how to install the missing dependencies. + list[types.ModuleType | Callable[..., typing.Any]]: Returns a list of the imported modules, types and/or functions. If the import of + the module fails, dummies are returned that will raise an exception when used, telling the user how to install the missing dependencies. """ def import_or_stub( module_name: str, type_and_function_names: str | tuple[str, ...] | None = None -) -> list[ModuleType | Callable[..., Any]] | ModuleType | Callable[..., Any]: +) -> list[types.ModuleType | Callable[..., typing.Any]] | types.ModuleType | Callable[..., typing.Any]: """Tries to import a module, or types and/or functions from a module. If the import fails, the requested module, or types and/or functions are replaced with dummies that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved module, or types and/or functions will raise an exception that tells the user how to install the missing dependencies for the functionality to work. Args: module_name (str): The name of the module that is to be imported or from which the specified types and/or functions are to be imported. - type_and_function_names (str | tuple[str, ...] | None, optional): The names of the types and/or functions that are to be imported from the + type_and_function_names (str | tuple[str, ...] | None): The names of the types and/or functions that are to be imported from the module, e.g., `'function'` or `('function', 'type')`. If this argument is not provided, the entire module is imported. If the module, or - the types and/or functions cannot be imported, dummies will be returned that will raise an exception when used. Defaults to `None`. + the types and/or functions cannot be imported, dummies will be returned that will raise an exception when used. Defaults to + :py:obj:`None`. Raises: - ImportError: If a type or function cannot be imported from a module that is installed, an ``ImportError`` is raised. This is done, instead of - stubbing the imported type or function, because this always indicates a bug in the code and cannot be fixed by the user installing a - missing dependency. + ImportError: If a type or function cannot be imported from a module that is installed, an :py:class:`ImportError` is raised. This is done, + instead of stubbing the imported type or function, because this always indicates a bug in the code and cannot be fixed by the user + installing a missing dependency. Returns: - list[ModuleType | Callable[..., Any]] | ModuleType | Callable[..., Any]: Returns the imported module, or types and/or functions that were - imported from the module. If the import of the module fails, dummies for the module, or types and/or functions are returned that will - raise an exception when used, telling the user how to install the missing dependencies. + list[types.ModuleType | Callable[..., typing.Any]] | types.ModuleType | Callable[..., typing.Any]: Returns the imported + module, or types and/or functions that were imported from the module. If the import of the module fails, dummies for the module, or types + and/or functions are returned that will raise an exception when used, telling the user how to install the missing dependencies. """ # Creates a list of the modules, types, and functions that were imported - modules_types_and_functions: list[ModuleType | type | Callable[..., Any]] = [] + modules_types_and_functions: list[types.ModuleType | type | Callable[..., typing.Any]] = [] # If a list of types and functions to import was provided, then they are imported (or replaced by dummies) and added to the list if type_and_function_names is not None: diff --git a/source/pyproject.toml b/source/pyproject.toml index 5c4bc79..2754ea9 100644 --- a/source/pyproject.toml +++ b/source/pyproject.toml @@ -101,7 +101,7 @@ dev = [ # Dependencies required for testing testing = [ - "tox>=4.25.0,<5.0.0", + "tox>=4.26.0,<5.0.0", "tox-uv>=1.25.0,<2.0.0", "pytest>=8.3.5,<9.0.0", "coverage>=7.8.0,<8.0.0", @@ -110,11 +110,11 @@ testing = [ # Dependencies required for linting linting = [ - "pylint>=3.3.6,<4.0.0", + "pylint>=3.3.7,<4.0.0", "pycodestyle>=2.13.0,<3.0.0", "pydoclint>=0.6.6,<1.0.0", "mypy>=1.15.0,<2.0.0", - "scipy-stubs>=1.15.2.2,<2.0.0", + "scipy-stubs>=1.15.3.0,<2.0.0", "pytest-mypy>=1.0.1,<2.0.0" ] @@ -132,7 +132,7 @@ docs = [ requires = [ "hatchling>=1.27.0,<2.0.0", "hatch-vcs>=0.4.0,<1.0.0", - "hatch-fancy-pypi-readme>=24.1.0,<25.0.0" + "hatch-fancy-pypi-readme>=25.1.0,<26.0.0" ] build-backend = "hatchling.build" diff --git a/source/tox.ini b/source/tox.ini index 265d48f..de1ac13 100644 --- a/source/tox.ini +++ b/source/tox.ini @@ -55,6 +55,7 @@ commands = sphinx-build \ --color \ --fail-on-warning \ + --fresh-env \ --keep-going \ --doctree-dir "../docs/doctree" \ --builder html \ @@ -79,7 +80,7 @@ commands = corelay \ "../tests/unit_tests" \ "../docs/source/conf.py" \ - "../example" + "../docs/examples" # A test environment that will run the PyCodeStyle linter on the REST API backend, the unit tests, the setup script, and the Sphinx configuration file [testenv:pycodestyle] @@ -91,7 +92,7 @@ commands = corelay \ "../tests/unit_tests" \ "../docs/source/conf.py" \ - "../example" + "../docs/examples" # A test environment that will run the PyDocLint docstring linter on the REST API backend, the unit tests, the setup script, and the Sphinx # configuration file @@ -104,7 +105,7 @@ commands = corelay \ "../tests/unit_tests" \ "../docs/source/conf.py" \ - "../example" + "../docs/examples" # A test environment that will run the MyPy static type checker on the REST API backend, the unit tests, the setup script, and the Sphinx # configuration file @@ -117,4 +118,4 @@ commands = corelay \ "../tests/unit_tests" \ "../docs/source/conf.py" \ - "../example" + "../docs/examples" diff --git a/source/uv.lock b/source/uv.lock index 3f1b980..9c9fa77 100644 --- a/source/uv.lock +++ b/source/uv.lock @@ -10,102 +10,102 @@ resolution-markers = [ name = "alabaster" version = "0.7.16" source = { registry = "https://pypi.org/simple" } -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" } +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" }, + { 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 = "astroid" version = "3.3.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731, upload_time = "2025-03-09T11:54:36.388Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731, upload-time = "2025-03-09T11:54:36.388Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339, upload_time = "2025-03-09T11:54:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339, upload-time = "2025-03-09T11:54:34.489Z" }, ] [[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" } +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" }, + { 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 = "cachetools" version = "5.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload_time = "2025-02-20T21:01:19.524Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload_time = "2025-02-20T21:01:16.647Z" }, + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, ] [[package]] name = "certifi" version = "2025.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload_time = "2025-01-31T02:16:47.166Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload_time = "2025-01-31T02:16:45.015Z" }, + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" }, ] [[package]] name = "chardet" version = "5.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload_time = "2023-08-01T19:23:02.662Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload_time = "2023-08-01T19:23:00.661Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload_time = "2024-12-24T18:12:35.43Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload_time = "2024-12-24T18:10:12.838Z" }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload_time = "2024-12-24T18:10:14.101Z" }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload_time = "2024-12-24T18:10:15.512Z" }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload_time = "2024-12-24T18:10:18.369Z" }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload_time = "2024-12-24T18:10:19.743Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload_time = "2024-12-24T18:10:21.139Z" }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload_time = "2024-12-24T18:10:22.382Z" }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload_time = "2024-12-24T18:10:24.802Z" }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload_time = "2024-12-24T18:10:26.124Z" }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload_time = "2024-12-24T18:10:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload_time = "2024-12-24T18:10:32.679Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload_time = "2024-12-24T18:10:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload_time = "2024-12-24T18:10:37.574Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload_time = "2024-12-24T18:10:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload_time = "2024-12-24T18:10:44.272Z" }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload_time = "2024-12-24T18:10:45.492Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload_time = "2024-12-24T18:10:47.898Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload_time = "2024-12-24T18:10:50.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload_time = "2024-12-24T18:10:52.541Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload_time = "2024-12-24T18:10:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload_time = "2024-12-24T18:10:55.048Z" }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload_time = "2024-12-24T18:10:57.647Z" }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload_time = "2024-12-24T18:10:59.43Z" }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload_time = "2024-12-24T18:11:00.676Z" }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload_time = "2024-12-24T18:11:01.952Z" }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload_time = "2024-12-24T18:11:03.142Z" }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload_time = "2024-12-24T18:11:05.834Z" }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload_time = "2024-12-24T18:11:07.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload_time = "2024-12-24T18:11:08.374Z" }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload_time = "2024-12-24T18:11:09.831Z" }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload_time = "2024-12-24T18:11:12.03Z" }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload_time = "2024-12-24T18:11:13.372Z" }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload_time = "2024-12-24T18:11:14.628Z" }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload_time = "2024-12-24T18:11:17.672Z" }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload_time = "2024-12-24T18:11:18.989Z" }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload_time = "2024-12-24T18:11:21.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload_time = "2024-12-24T18:11:22.774Z" }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload_time = "2024-12-24T18:11:24.139Z" }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload_time = "2024-12-24T18:11:26.535Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload_time = "2024-12-24T18:12:32.852Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload-time = "2024-12-24T18:10:12.838Z" }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload-time = "2024-12-24T18:10:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload-time = "2024-12-24T18:10:15.512Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload-time = "2024-12-24T18:10:18.369Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload-time = "2024-12-24T18:10:19.743Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload-time = "2024-12-24T18:10:21.139Z" }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload-time = "2024-12-24T18:10:22.382Z" }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload-time = "2024-12-24T18:10:24.802Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload-time = "2024-12-24T18:10:26.124Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload-time = "2024-12-24T18:10:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload-time = "2024-12-24T18:10:32.679Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload-time = "2024-12-24T18:10:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload-time = "2024-12-24T18:10:37.574Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload-time = "2024-12-24T18:10:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] [[package]] @@ -115,18 +115,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload_time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload_time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, ] [[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" } +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" }, + { 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]] @@ -136,51 +136,51 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -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/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/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" }, +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/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/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]] @@ -266,17 +266,17 @@ dev = [ { name = "mypy", specifier = ">=1.15.0,<2.0.0" }, { name = "pycodestyle", specifier = ">=2.13.0,<3.0.0" }, { name = "pydoclint", specifier = ">=0.6.6,<1.0.0" }, - { name = "pylint", specifier = ">=3.3.6,<4.0.0" }, + { name = "pylint", specifier = ">=3.3.7,<4.0.0" }, { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, { name = "pytest-cov", specifier = ">=6.1.1,<7.0.0" }, { name = "pytest-mypy", specifier = ">=1.0.1,<2.0.0" }, - { name = "scipy-stubs", specifier = ">=1.15.2.2,<2.0.0" }, + { name = "scipy-stubs", specifier = ">=1.15.3.0,<2.0.0" }, { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, { name = "sphinx-copybutton", specifier = ">=0.5.2,<1.0.0" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2,<4.0.0" }, { name = "sphinxcontrib-bibtex", specifier = ">=2.6.3,<3.0.0" }, { name = "sphinxcontrib-datatemplates", specifier = ">=0.11.0,<1.0.0" }, - { name = "tox", specifier = ">=4.25.0,<5.0.0" }, + { name = "tox", specifier = ">=4.26.0,<5.0.0" }, { name = "tox-uv", specifier = ">=1.25.0,<2.0.0" }, ] docs = [ @@ -290,15 +290,15 @@ linting = [ { name = "mypy", specifier = ">=1.15.0,<2.0.0" }, { name = "pycodestyle", specifier = ">=2.13.0,<3.0.0" }, { name = "pydoclint", specifier = ">=0.6.6,<1.0.0" }, - { name = "pylint", specifier = ">=3.3.6,<4.0.0" }, + { name = "pylint", specifier = ">=3.3.7,<4.0.0" }, { name = "pytest-mypy", specifier = ">=1.0.1,<2.0.0" }, - { name = "scipy-stubs", specifier = ">=1.15.2.2,<2.0.0" }, + { name = "scipy-stubs", specifier = ">=1.15.3.0,<2.0.0" }, ] testing = [ { name = "coverage", specifier = ">=7.8.0,<8.0.0" }, { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, { name = "pytest-cov", specifier = ">=6.1.1,<7.0.0" }, - { name = "tox", specifier = ">=4.25.0,<5.0.0" }, + { name = "tox", specifier = ">=4.26.0,<5.0.0" }, { name = "tox-uv", specifier = ">=1.25.0,<2.0.0" }, ] @@ -306,146 +306,146 @@ testing = [ name = "coverage" version = "7.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload_time = "2025-03-30T20:36:45.376Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload_time = "2025-03-30T20:35:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload_time = "2025-03-30T20:35:14.18Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload_time = "2025-03-30T20:35:15.616Z" }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload_time = "2025-03-30T20:35:18.648Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload_time = "2025-03-30T20:35:20.131Z" }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload_time = "2025-03-30T20:35:21.636Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload_time = "2025-03-30T20:35:23.525Z" }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload_time = "2025-03-30T20:35:25.09Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload_time = "2025-03-30T20:35:26.914Z" }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload_time = "2025-03-30T20:35:28.498Z" }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload_time = "2025-03-30T20:35:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload_time = "2025-03-30T20:35:31.912Z" }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload_time = "2025-03-30T20:35:33.455Z" }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload_time = "2025-03-30T20:35:35.354Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload_time = "2025-03-30T20:35:37.121Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload_time = "2025-03-30T20:35:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload_time = "2025-03-30T20:35:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload_time = "2025-03-30T20:35:42.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload_time = "2025-03-30T20:35:44.216Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload_time = "2025-03-30T20:35:45.797Z" }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload_time = "2025-03-30T20:35:47.417Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload_time = "2025-03-30T20:35:49.002Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload_time = "2025-03-30T20:35:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload_time = "2025-03-30T20:35:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload_time = "2025-03-30T20:35:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload_time = "2025-03-30T20:35:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload_time = "2025-03-30T20:35:57.801Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload_time = "2025-03-30T20:35:59.378Z" }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload_time = "2025-03-30T20:36:01.005Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload_time = "2025-03-30T20:36:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload_time = "2025-03-30T20:36:04.638Z" }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload_time = "2025-03-30T20:36:06.503Z" }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload_time = "2025-03-30T20:36:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload_time = "2025-03-30T20:36:09.781Z" }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload_time = "2025-03-30T20:36:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload_time = "2025-03-30T20:36:13.86Z" }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload_time = "2025-03-30T20:36:16.074Z" }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload_time = "2025-03-30T20:36:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload_time = "2025-03-30T20:36:19.644Z" }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload_time = "2025-03-30T20:36:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload_time = "2025-03-30T20:36:41.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload_time = "2025-03-30T20:36:43.61Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, + { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" }, + { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" }, + { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" }, + { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, + { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, + { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, + { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, ] [[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" } +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" }, + { 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" } +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" }, + { 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 = "dill" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload_time = "2025-04-16T00:41:48.867Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload_time = "2025-04-16T00:41:47.671Z" }, + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, ] [[package]] name = "distlib" version = "0.3.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload_time = "2024-10-09T18:35:47.551Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload_time = "2024-10-09T18:35:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, ] [[package]] name = "docstring-parser-fork" version = "0.0.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/72/61f7243ad62e14d527f93304cd4f333e681295aa3ef9bcc4afc36c07001a/docstring_parser_fork-0.0.12.tar.gz", hash = "sha256:b44c5e0be64ae80f395385f01497d381bd094a57221fd9ff020987d06857b2a0", size = 31608, upload_time = "2025-01-13T07:57:43.351Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/72/61f7243ad62e14d527f93304cd4f333e681295aa3ef9bcc4afc36c07001a/docstring_parser_fork-0.0.12.tar.gz", hash = "sha256:b44c5e0be64ae80f395385f01497d381bd094a57221fd9ff020987d06857b2a0", size = 31608, upload-time = "2025-01-13T07:57:43.351Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/0d/eed05d4b1065f11f8bef2f57440759d4dacc73c22780764948bfa4aaa304/docstring_parser_fork-0.0.12-py3-none-any.whl", hash = "sha256:55d7cbbc8b367655efd64372b9a0b33a49bae930a8ddd5cdc4c6112312e28a87", size = 42185, upload_time = "2025-01-13T07:57:39.993Z" }, + { url = "https://files.pythonhosted.org/packages/50/0d/eed05d4b1065f11f8bef2f57440759d4dacc73c22780764948bfa4aaa304/docstring_parser_fork-0.0.12-py3-none-any.whl", hash = "sha256:55d7cbbc8b367655efd64372b9a0b33a49bae930a8ddd5cdc4c6112312e28a87", size = 42185, upload-time = "2025-01-13T07:57:39.993Z" }, ] [[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" } +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" }, + { 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 = "filelock" version = "3.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload_time = "2025-03-14T07:11:40.47Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload_time = "2025-03-14T07:11:39.145Z" }, + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, ] [[package]] name = "fonttools" version = "4.57.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448, upload_time = "2025-04-03T11:07:13.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392, upload_time = "2025-04-03T11:05:45.715Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609, upload_time = "2025-04-03T11:05:47.977Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292, upload_time = "2025-04-03T11:05:49.921Z" }, - { url = "https://files.pythonhosted.org/packages/41/b8/d5933559303a4ab18c799105f4c91ee0318cc95db4a2a09e300116625e7a/fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746", size = 4902503, upload_time = "2025-04-03T11:05:52.17Z" }, - { url = "https://files.pythonhosted.org/packages/32/13/acb36bfaa316f481153ce78de1fa3926a8bad42162caa3b049e1afe2408b/fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344", size = 5077351, upload_time = "2025-04-03T11:05:54.162Z" }, - { url = "https://files.pythonhosted.org/packages/b5/23/6d383a2ca83b7516d73975d8cca9d81a01acdcaa5e4db8579e4f3de78518/fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f", size = 5275067, upload_time = "2025-04-03T11:05:57.375Z" }, - { url = "https://files.pythonhosted.org/packages/bc/ca/31b8919c6da0198d5d522f1d26c980201378c087bdd733a359a1e7485769/fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36", size = 2158263, upload_time = "2025-04-03T11:05:59.567Z" }, - { url = "https://files.pythonhosted.org/packages/13/4c/de2612ea2216eb45cfc8eb91a8501615dd87716feaf5f8fb65cbca576289/fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d", size = 2204968, upload_time = "2025-04-03T11:06:02.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824, upload_time = "2025-04-03T11:06:03.782Z" }, - { url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072, upload_time = "2025-04-03T11:06:05.533Z" }, - { url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020, upload_time = "2025-04-03T11:06:07.249Z" }, - { url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096, upload_time = "2025-04-03T11:06:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356, upload_time = "2025-04-03T11:06:11.294Z" }, - { url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546, upload_time = "2025-04-03T11:06:13.6Z" }, - { url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776, upload_time = "2025-04-03T11:06:15.643Z" }, - { url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956, upload_time = "2025-04-03T11:06:17.534Z" }, - { url = "https://files.pythonhosted.org/packages/e9/2f/11439f3af51e4bb75ac9598c29f8601aa501902dcedf034bdc41f47dd799/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef", size = 2739175, upload_time = "2025-04-03T11:06:19.583Z" }, - { url = "https://files.pythonhosted.org/packages/25/52/677b55a4c0972dc3820c8dba20a29c358197a78229daa2ea219fdb19e5d5/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c", size = 2276583, upload_time = "2025-04-03T11:06:21.753Z" }, - { url = "https://files.pythonhosted.org/packages/64/79/184555f8fa77b827b9460a4acdbbc0b5952bb6915332b84c615c3a236826/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72", size = 4766437, upload_time = "2025-04-03T11:06:23.521Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ad/c25116352f456c0d1287545a7aa24e98987b6d99c5b0456c4bd14321f20f/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817", size = 4838431, upload_time = "2025-04-03T11:06:25.423Z" }, - { url = "https://files.pythonhosted.org/packages/53/ae/398b2a833897297797a44f519c9af911c2136eb7aa27d3f1352c6d1129fa/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9", size = 4951011, upload_time = "2025-04-03T11:06:27.41Z" }, - { url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679, upload_time = "2025-04-03T11:06:29.804Z" }, - { url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833, upload_time = "2025-04-03T11:06:31.737Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799, upload_time = "2025-04-03T11:06:34.784Z" }, - { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605, upload_time = "2025-04-03T11:07:11.341Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448, upload-time = "2025-04-03T11:07:13.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392, upload-time = "2025-04-03T11:05:45.715Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609, upload-time = "2025-04-03T11:05:47.977Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292, upload-time = "2025-04-03T11:05:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/b8/d5933559303a4ab18c799105f4c91ee0318cc95db4a2a09e300116625e7a/fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746", size = 4902503, upload-time = "2025-04-03T11:05:52.17Z" }, + { url = "https://files.pythonhosted.org/packages/32/13/acb36bfaa316f481153ce78de1fa3926a8bad42162caa3b049e1afe2408b/fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344", size = 5077351, upload-time = "2025-04-03T11:05:54.162Z" }, + { url = "https://files.pythonhosted.org/packages/b5/23/6d383a2ca83b7516d73975d8cca9d81a01acdcaa5e4db8579e4f3de78518/fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f", size = 5275067, upload-time = "2025-04-03T11:05:57.375Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ca/31b8919c6da0198d5d522f1d26c980201378c087bdd733a359a1e7485769/fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36", size = 2158263, upload-time = "2025-04-03T11:05:59.567Z" }, + { url = "https://files.pythonhosted.org/packages/13/4c/de2612ea2216eb45cfc8eb91a8501615dd87716feaf5f8fb65cbca576289/fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d", size = 2204968, upload-time = "2025-04-03T11:06:02.16Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824, upload-time = "2025-04-03T11:06:03.782Z" }, + { url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072, upload-time = "2025-04-03T11:06:05.533Z" }, + { url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020, upload-time = "2025-04-03T11:06:07.249Z" }, + { url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096, upload-time = "2025-04-03T11:06:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356, upload-time = "2025-04-03T11:06:11.294Z" }, + { url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546, upload-time = "2025-04-03T11:06:13.6Z" }, + { url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776, upload-time = "2025-04-03T11:06:15.643Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956, upload-time = "2025-04-03T11:06:17.534Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2f/11439f3af51e4bb75ac9598c29f8601aa501902dcedf034bdc41f47dd799/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef", size = 2739175, upload-time = "2025-04-03T11:06:19.583Z" }, + { url = "https://files.pythonhosted.org/packages/25/52/677b55a4c0972dc3820c8dba20a29c358197a78229daa2ea219fdb19e5d5/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c", size = 2276583, upload-time = "2025-04-03T11:06:21.753Z" }, + { url = "https://files.pythonhosted.org/packages/64/79/184555f8fa77b827b9460a4acdbbc0b5952bb6915332b84c615c3a236826/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72", size = 4766437, upload-time = "2025-04-03T11:06:23.521Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/c25116352f456c0d1287545a7aa24e98987b6d99c5b0456c4bd14321f20f/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817", size = 4838431, upload-time = "2025-04-03T11:06:25.423Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/398b2a833897297797a44f519c9af911c2136eb7aa27d3f1352c6d1129fa/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9", size = 4951011, upload-time = "2025-04-03T11:06:27.41Z" }, + { url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679, upload-time = "2025-04-03T11:06:29.804Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833, upload-time = "2025-04-03T11:06:31.737Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799, upload-time = "2025-04-03T11:06:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605, upload-time = "2025-04-03T11:07:11.341Z" }, ] [[package]] @@ -455,23 +455,23 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876, upload_time = "2025-02-18T16:04:01.824Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876, upload-time = "2025-02-18T16:04:01.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922, upload_time = "2025-02-18T16:02:36.376Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619, upload_time = "2025-02-18T16:02:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366, upload_time = "2025-02-18T16:02:44.544Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/c99f662d4832c8835453cf3476f95daa28372023bda4aa1fca9e97c24f09/h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782ff0ac39f455f21fd1c8ebc007328f65f43d56718a89327eec76677ebf238a", size = 4509058, upload_time = "2025-02-18T16:02:49.035Z" }, - { url = "https://files.pythonhosted.org/packages/56/89/e3ff23e07131ff73a72a349be9639e4de84e163af89c1c218b939459a98a/h5py-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:22ffe2a25770a2d67213a1b94f58006c14dce06933a42d2aaa0318c5868d1508", size = 2966428, upload_time = "2025-02-18T16:02:52.061Z" }, - { url = "https://files.pythonhosted.org/packages/d8/20/438f6366ba4ded80eadb38f8927f5e2cd6d2e087179552f20ae3dbcd5d5b/h5py-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:477c58307b6b9a2509c59c57811afb9f598aedede24a67da808262dfa0ee37b4", size = 3384442, upload_time = "2025-02-18T16:02:56.545Z" }, - { url = "https://files.pythonhosted.org/packages/10/13/cc1cb7231399617d9951233eb12fddd396ff5d4f7f057ee5d2b1ca0ee7e7/h5py-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57c4c74f627c616f02b7aec608a8c706fe08cb5b0ba7c08555a4eb1dde20805a", size = 2917567, upload_time = "2025-02-18T16:03:00.079Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d9/aed99e1c858dc698489f916eeb7c07513bc864885d28ab3689d572ba0ea0/h5py-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:357e6dc20b101a805ccfd0024731fbaf6e8718c18c09baf3b5e4e9d198d13fca", size = 4669544, upload_time = "2025-02-18T16:03:05.675Z" }, - { url = "https://files.pythonhosted.org/packages/a7/da/3c137006ff5f0433f0fb076b1ebe4a7bf7b5ee1e8811b5486af98b500dd5/h5py-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6f13f9b5ce549448c01e4dfe08ea8d1772e6078799af2c1c8d09e941230a90d", size = 4932139, upload_time = "2025-02-18T16:03:10.129Z" }, - { url = "https://files.pythonhosted.org/packages/25/61/d897952629cae131c19d4c41b2521e7dd6382f2d7177c87615c2e6dced1a/h5py-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:21daf38171753899b5905f3d82c99b0b1ec2cbbe282a037cad431feb620e62ec", size = 2954179, upload_time = "2025-02-18T16:03:13.716Z" }, - { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040, upload_time = "2025-02-18T16:03:20.579Z" }, - { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766, upload_time = "2025-02-18T16:03:26.831Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255, upload_time = "2025-02-18T16:03:31.903Z" }, - { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580, upload_time = "2025-02-18T16:03:36.429Z" }, - { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890, upload_time = "2025-02-18T16:03:41.037Z" }, + { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922, upload-time = "2025-02-18T16:02:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619, upload-time = "2025-02-18T16:02:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366, upload-time = "2025-02-18T16:02:44.544Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/c99f662d4832c8835453cf3476f95daa28372023bda4aa1fca9e97c24f09/h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782ff0ac39f455f21fd1c8ebc007328f65f43d56718a89327eec76677ebf238a", size = 4509058, upload-time = "2025-02-18T16:02:49.035Z" }, + { url = "https://files.pythonhosted.org/packages/56/89/e3ff23e07131ff73a72a349be9639e4de84e163af89c1c218b939459a98a/h5py-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:22ffe2a25770a2d67213a1b94f58006c14dce06933a42d2aaa0318c5868d1508", size = 2966428, upload-time = "2025-02-18T16:02:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/d8/20/438f6366ba4ded80eadb38f8927f5e2cd6d2e087179552f20ae3dbcd5d5b/h5py-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:477c58307b6b9a2509c59c57811afb9f598aedede24a67da808262dfa0ee37b4", size = 3384442, upload-time = "2025-02-18T16:02:56.545Z" }, + { url = "https://files.pythonhosted.org/packages/10/13/cc1cb7231399617d9951233eb12fddd396ff5d4f7f057ee5d2b1ca0ee7e7/h5py-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57c4c74f627c616f02b7aec608a8c706fe08cb5b0ba7c08555a4eb1dde20805a", size = 2917567, upload-time = "2025-02-18T16:03:00.079Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d9/aed99e1c858dc698489f916eeb7c07513bc864885d28ab3689d572ba0ea0/h5py-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:357e6dc20b101a805ccfd0024731fbaf6e8718c18c09baf3b5e4e9d198d13fca", size = 4669544, upload-time = "2025-02-18T16:03:05.675Z" }, + { url = "https://files.pythonhosted.org/packages/a7/da/3c137006ff5f0433f0fb076b1ebe4a7bf7b5ee1e8811b5486af98b500dd5/h5py-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6f13f9b5ce549448c01e4dfe08ea8d1772e6078799af2c1c8d09e941230a90d", size = 4932139, upload-time = "2025-02-18T16:03:10.129Z" }, + { url = "https://files.pythonhosted.org/packages/25/61/d897952629cae131c19d4c41b2521e7dd6382f2d7177c87615c2e6dced1a/h5py-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:21daf38171753899b5905f3d82c99b0b1ec2cbbe282a037cad431feb620e62ec", size = 2954179, upload-time = "2025-02-18T16:03:13.716Z" }, + { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040, upload-time = "2025-02-18T16:03:20.579Z" }, + { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766, upload-time = "2025-02-18T16:03:26.831Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255, upload-time = "2025-02-18T16:03:31.903Z" }, + { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580, upload-time = "2025-02-18T16:03:36.429Z" }, + { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890, upload-time = "2025-02-18T16:03:41.037Z" }, ] [[package]] @@ -484,22 +484,22 @@ dependencies = [ { name = "scikit-learn" }, { name = "scipy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/84/6b010387b795f774e1ec695df3c8660c15abd041783647d5e7e4076bfc6b/hdbscan-0.8.40.tar.gz", hash = "sha256:c9e383ff17beee0591075ff65d524bda5b5a35dfb01d218245a7ba30c8d48a17", size = 6904096, upload_time = "2024-11-18T16:14:05.384Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/84/6b010387b795f774e1ec695df3c8660c15abd041783647d5e7e4076bfc6b/hdbscan-0.8.40.tar.gz", hash = "sha256:c9e383ff17beee0591075ff65d524bda5b5a35dfb01d218245a7ba30c8d48a17", size = 6904096, upload-time = "2024-11-18T16:14:05.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/6b/88b8c8023c0c0b27589ad83c82084a1b751917a3e09bdf7fcacf7e6bd523/hdbscan-0.8.40-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e958f0d7a33cd2b5e8e927b47f7360bf8a3e7d72355dd65a701e8aabe407b27", size = 1491349, upload_time = "2024-11-18T16:16:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ef/32c8a0b3dc6e6c4e433b85b30c3723d8eb48d115c0185b82ab89e1a0ef89/hdbscan-0.8.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0d6197ee045b173e1f16e6884386f335a56091e373a839dd24f7331a8fa9ed", size = 4576215, upload_time = "2024-11-18T16:14:11.241Z" }, - { url = "https://files.pythonhosted.org/packages/64/b1/96c347c7740efa1ac803be64155159284f92fafcff88c1077344e64eead5/hdbscan-0.8.40-cp311-cp311-win_amd64.whl", hash = "sha256:127cbe8c858dc77adfde33a3e1ce4f3bea810f78b01d2bd47b1147d4b5a50472", size = 732173, upload_time = "2024-11-18T16:18:40.361Z" }, - { url = "https://files.pythonhosted.org/packages/33/ff/4739886abb990dc6feb7b02eafb38a7eaf090fffef6336e70a03d693f433/hdbscan-0.8.40-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:353eaa22e42bee69df095744dbb8b29360e516bd9dcb84580dceeeb755f004cc", size = 1497291, upload_time = "2024-11-18T16:16:54.731Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cb/6b4254f8a33e075118512e55acf3485c155ea52c6c35d69a985bdc59297c/hdbscan-0.8.40-cp312-cp312-win_amd64.whl", hash = "sha256:1b55a935ed7b329adac52072e1c4028979dfc54312ca08de2deece9c97d6ebb1", size = 726198, upload_time = "2024-11-18T16:18:09.99Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/88b8c8023c0c0b27589ad83c82084a1b751917a3e09bdf7fcacf7e6bd523/hdbscan-0.8.40-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e958f0d7a33cd2b5e8e927b47f7360bf8a3e7d72355dd65a701e8aabe407b27", size = 1491349, upload-time = "2024-11-18T16:16:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ef/32c8a0b3dc6e6c4e433b85b30c3723d8eb48d115c0185b82ab89e1a0ef89/hdbscan-0.8.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0d6197ee045b173e1f16e6884386f335a56091e373a839dd24f7331a8fa9ed", size = 4576215, upload-time = "2024-11-18T16:14:11.241Z" }, + { url = "https://files.pythonhosted.org/packages/64/b1/96c347c7740efa1ac803be64155159284f92fafcff88c1077344e64eead5/hdbscan-0.8.40-cp311-cp311-win_amd64.whl", hash = "sha256:127cbe8c858dc77adfde33a3e1ce4f3bea810f78b01d2bd47b1147d4b5a50472", size = 732173, upload-time = "2024-11-18T16:18:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/33/ff/4739886abb990dc6feb7b02eafb38a7eaf090fffef6336e70a03d693f433/hdbscan-0.8.40-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:353eaa22e42bee69df095744dbb8b29360e516bd9dcb84580dceeeb755f004cc", size = 1497291, upload-time = "2024-11-18T16:16:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cb/6b4254f8a33e075118512e55acf3485c155ea52c6c35d69a985bdc59297c/hdbscan-0.8.40-cp312-cp312-win_amd64.whl", hash = "sha256:1b55a935ed7b329adac52072e1c4028979dfc54312ca08de2deece9c97d6ebb1", size = 726198, upload-time = "2024-11-18T16:18:09.99Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] @@ -510,36 +510,36 @@ dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload_time = "2025-01-20T02:42:37.089Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload-time = "2025-01-20T02:42:37.089Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload_time = "2025-01-20T02:42:34.931Z" }, + { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" }, ] [[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" } +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" }, + { 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 = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -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" } +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" }, + { 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 = "isort" version = "5.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303, upload_time = "2023-12-13T20:37:26.124Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303, upload-time = "2023-12-13T20:37:26.124Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload_time = "2023-12-13T20:37:23.244Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload-time = "2023-12-13T20:37:23.244Z" }, ] [[package]] @@ -549,93 +549,93 @@ 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" } +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" }, + { 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 = "joblib" version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621, upload_time = "2024-05-02T12:15:05.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621, upload-time = "2024-05-02T12:15:05.765Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817, upload_time = "2024-05-02T12:15:00.765Z" }, + { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817, upload-time = "2024-05-02T12:15:00.765Z" }, ] [[package]] name = "kiwisolver" version = "1.4.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload_time = "2024-12-24T18:30:51.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload_time = "2024-12-24T18:28:51.826Z" }, - { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload_time = "2024-12-24T18:28:54.256Z" }, - { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload_time = "2024-12-24T18:28:55.184Z" }, - { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload_time = "2024-12-24T18:28:57.493Z" }, - { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload_time = "2024-12-24T18:29:00.077Z" }, - { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload_time = "2024-12-24T18:29:01.401Z" }, - { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload_time = "2024-12-24T18:29:02.685Z" }, - { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload_time = "2024-12-24T18:29:04.113Z" }, - { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload_time = "2024-12-24T18:29:05.488Z" }, - { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload_time = "2024-12-24T18:29:06.79Z" }, - { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload_time = "2024-12-24T18:29:08.24Z" }, - { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload_time = "2024-12-24T18:29:09.653Z" }, - { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload_time = "2024-12-24T18:29:12.644Z" }, - { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload_time = "2024-12-24T18:29:14.089Z" }, - { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload_time = "2024-12-24T18:29:15.892Z" }, - { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload_time = "2024-12-24T18:29:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload_time = "2024-12-24T18:29:19.146Z" }, - { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload_time = "2024-12-24T18:29:20.096Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload_time = "2024-12-24T18:29:22.843Z" }, - { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload_time = "2024-12-24T18:29:24.463Z" }, - { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload_time = "2024-12-24T18:29:25.776Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload_time = "2024-12-24T18:29:27.202Z" }, - { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload_time = "2024-12-24T18:29:28.638Z" }, - { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload_time = "2024-12-24T18:29:30.368Z" }, - { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload_time = "2024-12-24T18:29:33.151Z" }, - { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload_time = "2024-12-24T18:29:34.584Z" }, - { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload_time = "2024-12-24T18:29:36.138Z" }, - { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload_time = "2024-12-24T18:29:39.991Z" }, - { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload_time = "2024-12-24T18:29:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload_time = "2024-12-24T18:29:44.38Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload_time = "2024-12-24T18:29:45.368Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload_time = "2024-12-24T18:29:46.37Z" }, - { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload_time = "2024-12-24T18:29:47.333Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload_time = "2024-12-24T18:29:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload_time = "2024-12-24T18:29:51.164Z" }, - { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload_time = "2024-12-24T18:29:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload_time = "2024-12-24T18:29:53.941Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload_time = "2024-12-24T18:29:56.523Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload_time = "2024-12-24T18:29:57.989Z" }, - { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload_time = "2024-12-24T18:29:59.393Z" }, - { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload_time = "2024-12-24T18:30:01.338Z" }, - { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload_time = "2024-12-24T18:30:04.574Z" }, - { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload_time = "2024-12-24T18:30:06.25Z" }, - { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload_time = "2024-12-24T18:30:07.535Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload_time = "2024-12-24T18:30:08.504Z" }, - { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload_time = "2024-12-24T18:30:09.508Z" }, - { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload_time = "2024-12-24T18:30:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload_time = "2024-12-24T18:30:14.886Z" }, - { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload_time = "2024-12-24T18:30:18.927Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload_time = "2024-12-24T18:30:22.102Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload_time = "2024-12-24T18:30:24.947Z" }, - { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload_time = "2024-12-24T18:30:26.286Z" }, - { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload_time = "2024-12-24T18:30:28.86Z" }, - { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload_time = "2024-12-24T18:30:30.34Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload_time = "2024-12-24T18:30:33.334Z" }, - { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload_time = "2024-12-24T18:30:34.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload_time = "2024-12-24T18:30:37.281Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload_time = "2024-12-24T18:30:40.019Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload-time = "2024-12-24T18:28:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload-time = "2024-12-24T18:28:54.256Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload-time = "2024-12-24T18:28:55.184Z" }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload-time = "2024-12-24T18:28:57.493Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload-time = "2024-12-24T18:29:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload-time = "2024-12-24T18:29:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload-time = "2024-12-24T18:29:02.685Z" }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload-time = "2024-12-24T18:29:04.113Z" }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload-time = "2024-12-24T18:29:05.488Z" }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload-time = "2024-12-24T18:29:06.79Z" }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload-time = "2024-12-24T18:29:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload-time = "2024-12-24T18:29:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload-time = "2024-12-24T18:29:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload-time = "2024-12-24T18:29:14.089Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload-time = "2024-12-24T18:29:15.892Z" }, + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, ] [[package]] name = "latexcodec" version = "3.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/e7/ed339caf3662976949e4fdbfdf4a6db818b8d2aa1cf2b5f73af89e936bba/latexcodec-3.0.0.tar.gz", hash = "sha256:917dc5fe242762cc19d963e6548b42d63a118028cdd3361d62397e3b638b6bc5", size = 31023, upload_time = "2024-03-06T14:51:39.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/e7/ed339caf3662976949e4fdbfdf4a6db818b8d2aa1cf2b5f73af89e936bba/latexcodec-3.0.0.tar.gz", hash = "sha256:917dc5fe242762cc19d963e6548b42d63a118028cdd3361d62397e3b638b6bc5", size = 31023, upload-time = "2024-03-06T14:51:39.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/bf/ea8887e9f31a8f93ca306699d11909c6140151393a4216f0d9f85a004077/latexcodec-3.0.0-py3-none-any.whl", hash = "sha256:6f3477ad5e61a0a99bd31a6a370c34e88733a6bad9c921a3ffcfacada12f41a7", size = 18150, upload_time = "2024-03-06T14:51:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/b0/bf/ea8887e9f31a8f93ca306699d11909c6140151393a4216f0d9f85a004077/latexcodec-3.0.0-py3-none-any.whl", hash = "sha256:6f3477ad5e61a0a99bd31a6a370c34e88733a6bad9c921a3ffcfacada12f41a7", size = 18150, upload-time = "2024-03-06T14:51:37.872Z" }, ] [[package]] @@ -645,80 +645,80 @@ 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" } +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" }, + { 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 = "llvmlite" version = "0.44.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880, upload_time = "2025-01-20T11:14:41.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880, upload-time = "2025-01-20T11:14:41.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/e2/86b245397052386595ad726f9742e5223d7aea999b18c518a50e96c3aca4/llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", size = 28132305, upload_time = "2025-01-20T11:12:53.936Z" }, - { url = "https://files.pythonhosted.org/packages/ff/ec/506902dc6870249fbe2466d9cf66d531265d0f3a1157213c8f986250c033/llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", size = 26201090, upload_time = "2025-01-20T11:12:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/99/fe/d030f1849ebb1f394bb3f7adad5e729b634fb100515594aca25c354ffc62/llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", size = 42361858, upload_time = "2025-01-20T11:13:07.623Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7a/ce6174664b9077fc673d172e4c888cb0b128e707e306bc33fff8c2035f0d/llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610", size = 41184200, upload_time = "2025-01-20T11:13:20.058Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c6/258801143975a6d09a373f2641237992496e15567b907a4d401839d671b8/llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", size = 30331193, upload_time = "2025-01-20T11:13:26.976Z" }, - { url = "https://files.pythonhosted.org/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297, upload_time = "2025-01-20T11:13:32.57Z" }, - { url = "https://files.pythonhosted.org/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105, upload_time = "2025-01-20T11:13:38.744Z" }, - { url = "https://files.pythonhosted.org/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901, upload_time = "2025-01-20T11:13:46.711Z" }, - { url = "https://files.pythonhosted.org/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247, upload_time = "2025-01-20T11:13:56.159Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380, upload_time = "2025-01-20T11:14:02.442Z" }, - { url = "https://files.pythonhosted.org/packages/89/24/4c0ca705a717514c2092b18476e7a12c74d34d875e05e4d742618ebbf449/llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516", size = 28132306, upload_time = "2025-01-20T11:14:09.035Z" }, - { url = "https://files.pythonhosted.org/packages/01/cf/1dd5a60ba6aee7122ab9243fd614abcf22f36b0437cbbe1ccf1e3391461c/llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e", size = 26201090, upload_time = "2025-01-20T11:14:15.401Z" }, - { url = "https://files.pythonhosted.org/packages/d2/1b/656f5a357de7135a3777bd735cc7c9b8f23b4d37465505bd0eaf4be9befe/llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf", size = 42361904, upload_time = "2025-01-20T11:14:22.949Z" }, - { url = "https://files.pythonhosted.org/packages/d8/e1/12c5f20cb9168fb3464a34310411d5ad86e4163c8ff2d14a2b57e5cc6bac/llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc", size = 41184245, upload_time = "2025-01-20T11:14:31.731Z" }, - { url = "https://files.pythonhosted.org/packages/d0/81/e66fc86539293282fd9cb7c9417438e897f369e79ffb62e1ae5e5154d4dd/llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930", size = 30331193, upload_time = "2025-01-20T11:14:38.578Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e2/86b245397052386595ad726f9742e5223d7aea999b18c518a50e96c3aca4/llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", size = 28132305, upload-time = "2025-01-20T11:12:53.936Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ec/506902dc6870249fbe2466d9cf66d531265d0f3a1157213c8f986250c033/llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427", size = 26201090, upload-time = "2025-01-20T11:12:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/99/fe/d030f1849ebb1f394bb3f7adad5e729b634fb100515594aca25c354ffc62/llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1", size = 42361858, upload-time = "2025-01-20T11:13:07.623Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/ce6174664b9077fc673d172e4c888cb0b128e707e306bc33fff8c2035f0d/llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610", size = 41184200, upload-time = "2025-01-20T11:13:20.058Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c6/258801143975a6d09a373f2641237992496e15567b907a4d401839d671b8/llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955", size = 30331193, upload-time = "2025-01-20T11:13:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297, upload-time = "2025-01-20T11:13:32.57Z" }, + { url = "https://files.pythonhosted.org/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105, upload-time = "2025-01-20T11:13:38.744Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901, upload-time = "2025-01-20T11:13:46.711Z" }, + { url = "https://files.pythonhosted.org/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247, upload-time = "2025-01-20T11:13:56.159Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380, upload-time = "2025-01-20T11:14:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/89/24/4c0ca705a717514c2092b18476e7a12c74d34d875e05e4d742618ebbf449/llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516", size = 28132306, upload-time = "2025-01-20T11:14:09.035Z" }, + { url = "https://files.pythonhosted.org/packages/01/cf/1dd5a60ba6aee7122ab9243fd614abcf22f36b0437cbbe1ccf1e3391461c/llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e", size = 26201090, upload-time = "2025-01-20T11:14:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1b/656f5a357de7135a3777bd735cc7c9b8f23b4d37465505bd0eaf4be9befe/llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf", size = 42361904, upload-time = "2025-01-20T11:14:22.949Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e1/12c5f20cb9168fb3464a34310411d5ad86e4163c8ff2d14a2b57e5cc6bac/llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc", size = 41184245, upload-time = "2025-01-20T11:14:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/d0/81/e66fc86539293282fd9cb7c9417438e897f369e79ffb62e1ae5e5154d4dd/llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930", size = 30331193, upload-time = "2025-01-20T11:14:38.578Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload_time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload_time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload_time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload_time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload_time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload_time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload_time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload_time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload_time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload_time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload_time = "2024-10-18T15:21:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload_time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload_time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload_time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload_time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload_time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload_time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload_time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload_time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload_time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload_time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload_time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload_time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload_time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload_time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload_time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload_time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload_time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload_time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload_time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload_time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload_time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload_time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload_time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload_time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload_time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload_time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload_time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload_time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload_time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload_time = "2024-10-18T15:21:42.784Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] [[package]] @@ -736,48 +736,48 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335, upload_time = "2025-02-27T19:19:51.038Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669, upload_time = "2025-02-27T19:18:34.346Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996, upload_time = "2025-02-27T19:18:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612, upload_time = "2025-02-27T19:18:39.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258, upload_time = "2025-02-27T19:18:43.217Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896, upload_time = "2025-02-27T19:18:45.852Z" }, - { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281, upload_time = "2025-02-27T19:18:48.919Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488, upload_time = "2025-02-27T19:18:51.436Z" }, - { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264, upload_time = "2025-02-27T19:18:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048, upload_time = "2025-02-27T19:18:56.536Z" }, - { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111, upload_time = "2025-02-27T19:18:59.439Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771, upload_time = "2025-02-27T19:19:01.944Z" }, - { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742, upload_time = "2025-02-27T19:19:04.632Z" }, - { url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112, upload_time = "2025-02-27T19:19:07.59Z" }, - { url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931, upload_time = "2025-02-27T19:19:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422, upload_time = "2025-02-27T19:19:12.738Z" }, - { url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819, upload_time = "2025-02-27T19:19:15.306Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782, upload_time = "2025-02-27T19:19:17.841Z" }, - { url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812, upload_time = "2025-02-27T19:19:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021, upload_time = "2025-02-27T19:19:23.412Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782, upload_time = "2025-02-27T19:19:28.33Z" }, - { url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901, upload_time = "2025-02-27T19:19:31.536Z" }, - { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864, upload_time = "2025-02-27T19:19:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487, upload_time = "2025-02-27T19:19:36.924Z" }, - { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832, upload_time = "2025-02-27T19:19:39.431Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335, upload-time = "2025-02-27T19:19:51.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669, upload-time = "2025-02-27T19:18:34.346Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996, upload-time = "2025-02-27T19:18:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612, upload-time = "2025-02-27T19:18:39.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258, upload-time = "2025-02-27T19:18:43.217Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896, upload-time = "2025-02-27T19:18:45.852Z" }, + { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281, upload-time = "2025-02-27T19:18:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488, upload-time = "2025-02-27T19:18:51.436Z" }, + { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264, upload-time = "2025-02-27T19:18:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048, upload-time = "2025-02-27T19:18:56.536Z" }, + { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111, upload-time = "2025-02-27T19:18:59.439Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771, upload-time = "2025-02-27T19:19:01.944Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742, upload-time = "2025-02-27T19:19:04.632Z" }, + { url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112, upload-time = "2025-02-27T19:19:07.59Z" }, + { url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931, upload-time = "2025-02-27T19:19:10.515Z" }, + { url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422, upload-time = "2025-02-27T19:19:12.738Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819, upload-time = "2025-02-27T19:19:15.306Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782, upload-time = "2025-02-27T19:19:17.841Z" }, + { url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812, upload-time = "2025-02-27T19:19:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021, upload-time = "2025-02-27T19:19:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782, upload-time = "2025-02-27T19:19:28.33Z" }, + { url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901, upload-time = "2025-02-27T19:19:31.536Z" }, + { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864, upload-time = "2025-02-27T19:19:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487, upload-time = "2025-02-27T19:19:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832, upload-time = "2025-02-27T19:19:39.431Z" }, ] [[package]] name = "mccabe" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload_time = "2022-01-24T01:14:51.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload_time = "2022-01-24T01:14:49.62Z" }, + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] [[package]] name = "metrohash-python" version = "1.1.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/b5/4002d16e71d6a4b1c14a37d204e25aba6508b12915fe0dd07fa703ca0fa1/metrohash-python-1.1.3.3.tar.gz", hash = "sha256:2003c9b2d07514c8228d901e3b32492ae42459e577e946ab91c0ef6eddfd0926", size = 45994, upload_time = "2022-02-25T01:22:01.063Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/b5/4002d16e71d6a4b1c14a37d204e25aba6508b12915fe0dd07fa703ca0fa1/metrohash-python-1.1.3.3.tar.gz", hash = "sha256:2003c9b2d07514c8228d901e3b32492ae42459e577e946ab91c0ef6eddfd0926", size = 45994, upload-time = "2022-02-25T01:22:01.063Z" } [[package]] name = "mypy" @@ -787,45 +787,45 @@ dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload_time = "2025-02-05T03:50:34.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload_time = "2025-02-05T03:50:17.287Z" }, - { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload_time = "2025-02-05T03:49:51.21Z" }, - { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload_time = "2025-02-05T03:50:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload_time = "2025-02-05T03:49:42.408Z" }, - { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload_time = "2025-02-05T03:49:07.707Z" }, - { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload_time = "2025-02-05T03:49:54.581Z" }, - { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload_time = "2025-02-05T03:50:28.25Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload_time = "2025-02-05T03:50:13.411Z" }, - { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload_time = "2025-02-05T03:50:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload_time = "2025-02-05T03:48:48.705Z" }, - { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload_time = "2025-02-05T03:49:03.628Z" }, - { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload_time = "2025-02-05T03:50:00.313Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload_time = "2025-02-05T03:48:55.789Z" }, - { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload_time = "2025-02-05T03:48:44.581Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload_time = "2025-02-05T03:49:25.514Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload_time = "2025-02-05T03:49:57.623Z" }, - { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload_time = "2025-02-05T03:48:52.361Z" }, - { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload_time = "2025-02-05T03:49:11.395Z" }, - { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload_time = "2025-02-05T03:50:08.348Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload-time = "2025-02-05T03:50:17.287Z" }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload-time = "2025-02-05T03:49:51.21Z" }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload-time = "2025-02-05T03:50:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload-time = "2025-02-05T03:49:42.408Z" }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload-time = "2025-02-05T03:49:07.707Z" }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload-time = "2025-02-05T03:49:54.581Z" }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" }, ] [[package]] name = "mypy-extensions" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload_time = "2023-02-04T12:11:27.157Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload_time = "2023-02-04T12:11:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, ] [[package]] name = "networkx" version = "3.4.2" source = { registry = "https://pypi.org/simple" } -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" } +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" }, + { 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]] @@ -836,71 +836,71 @@ dependencies = [ { name = "llvmlite" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615, upload_time = "2025-04-09T02:58:07.659Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615, upload-time = "2025-04-09T02:58:07.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/97/c99d1056aed767503c228f7099dc11c402906b42a4757fec2819329abb98/numba-0.61.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2", size = 2775825, upload_time = "2025-04-09T02:57:43.442Z" }, - { url = "https://files.pythonhosted.org/packages/95/9e/63c549f37136e892f006260c3e2613d09d5120672378191f2dc387ba65a2/numba-0.61.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", size = 2778695, upload_time = "2025-04-09T02:57:44.968Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/8740616c8436c86c1b9a62e72cb891177d2c34c2d24ddcde4c390371bf4c/numba-0.61.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", size = 3829227, upload_time = "2025-04-09T02:57:46.63Z" }, - { url = "https://files.pythonhosted.org/packages/fc/06/66e99ae06507c31d15ff3ecd1f108f2f59e18b6e08662cd5f8a5853fbd18/numba-0.61.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", size = 3523422, upload_time = "2025-04-09T02:57:48.222Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a4/2b309a6a9f6d4d8cfba583401c7c2f9ff887adb5d54d8e2e130274c0973f/numba-0.61.2-cp311-cp311-win_amd64.whl", hash = "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", size = 2831505, upload_time = "2025-04-09T02:57:50.108Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a0/c6b7b9c615cfa3b98c4c63f4316e3f6b3bbe2387740277006551784218cd/numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", size = 2776626, upload_time = "2025-04-09T02:57:51.857Z" }, - { url = "https://files.pythonhosted.org/packages/92/4a/fe4e3c2ecad72d88f5f8cd04e7f7cff49e718398a2fac02d2947480a00ca/numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", size = 2779287, upload_time = "2025-04-09T02:57:53.658Z" }, - { url = "https://files.pythonhosted.org/packages/9a/2d/e518df036feab381c23a624dac47f8445ac55686ec7f11083655eb707da3/numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", size = 3885928, upload_time = "2025-04-09T02:57:55.206Z" }, - { url = "https://files.pythonhosted.org/packages/10/0f/23cced68ead67b75d77cfcca3df4991d1855c897ee0ff3fe25a56ed82108/numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", size = 3577115, upload_time = "2025-04-09T02:57:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/68/1d/ddb3e704c5a8fb90142bf9dc195c27db02a08a99f037395503bfbc1d14b3/numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", size = 2831929, upload_time = "2025-04-09T02:57:58.45Z" }, - { url = "https://files.pythonhosted.org/packages/0b/f3/0fe4c1b1f2569e8a18ad90c159298d862f96c3964392a20d74fc628aee44/numba-0.61.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:3a10a8fc9afac40b1eac55717cece1b8b1ac0b946f5065c89e00bde646b5b154", size = 2771785, upload_time = "2025-04-09T02:57:59.96Z" }, - { url = "https://files.pythonhosted.org/packages/e9/71/91b277d712e46bd5059f8a5866862ed1116091a7cb03bd2704ba8ebe015f/numba-0.61.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d3bcada3c9afba3bed413fba45845f2fb9cd0d2b27dd58a1be90257e293d140", size = 2773289, upload_time = "2025-04-09T02:58:01.435Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e0/5ea04e7ad2c39288c0f0f9e8d47638ad70f28e275d092733b5817cf243c9/numba-0.61.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdbca73ad81fa196bd53dc12e3aaf1564ae036e0c125f237c7644fe64a4928ab", size = 3893918, upload_time = "2025-04-09T02:58:02.933Z" }, - { url = "https://files.pythonhosted.org/packages/17/58/064f4dcb7d7e9412f16ecf80ed753f92297e39f399c905389688cf950b81/numba-0.61.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f154aaea625fb32cfbe3b80c5456d514d416fcdf79733dd69c0df3a11348e9e", size = 3584056, upload_time = "2025-04-09T02:58:04.538Z" }, - { url = "https://files.pythonhosted.org/packages/af/a4/6d3a0f2d3989e62a18749e1e9913d5fa4910bbb3e3311a035baea6caf26d/numba-0.61.2-cp313-cp313-win_amd64.whl", hash = "sha256:59321215e2e0ac5fa928a8020ab00b8e57cda8a97384963ac0dfa4d4e6aa54e7", size = 2831846, upload_time = "2025-04-09T02:58:06.125Z" }, + { url = "https://files.pythonhosted.org/packages/3f/97/c99d1056aed767503c228f7099dc11c402906b42a4757fec2819329abb98/numba-0.61.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2", size = 2775825, upload-time = "2025-04-09T02:57:43.442Z" }, + { url = "https://files.pythonhosted.org/packages/95/9e/63c549f37136e892f006260c3e2613d09d5120672378191f2dc387ba65a2/numba-0.61.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b", size = 2778695, upload-time = "2025-04-09T02:57:44.968Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/8740616c8436c86c1b9a62e72cb891177d2c34c2d24ddcde4c390371bf4c/numba-0.61.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60", size = 3829227, upload-time = "2025-04-09T02:57:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/fc/06/66e99ae06507c31d15ff3ecd1f108f2f59e18b6e08662cd5f8a5853fbd18/numba-0.61.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18", size = 3523422, upload-time = "2025-04-09T02:57:48.222Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a4/2b309a6a9f6d4d8cfba583401c7c2f9ff887adb5d54d8e2e130274c0973f/numba-0.61.2-cp311-cp311-win_amd64.whl", hash = "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1", size = 2831505, upload-time = "2025-04-09T02:57:50.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/c6b7b9c615cfa3b98c4c63f4316e3f6b3bbe2387740277006551784218cd/numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", size = 2776626, upload-time = "2025-04-09T02:57:51.857Z" }, + { url = "https://files.pythonhosted.org/packages/92/4a/fe4e3c2ecad72d88f5f8cd04e7f7cff49e718398a2fac02d2947480a00ca/numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8", size = 2779287, upload-time = "2025-04-09T02:57:53.658Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2d/e518df036feab381c23a624dac47f8445ac55686ec7f11083655eb707da3/numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546", size = 3885928, upload-time = "2025-04-09T02:57:55.206Z" }, + { url = "https://files.pythonhosted.org/packages/10/0f/23cced68ead67b75d77cfcca3df4991d1855c897ee0ff3fe25a56ed82108/numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd", size = 3577115, upload-time = "2025-04-09T02:57:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/68/1d/ddb3e704c5a8fb90142bf9dc195c27db02a08a99f037395503bfbc1d14b3/numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18", size = 2831929, upload-time = "2025-04-09T02:57:58.45Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f3/0fe4c1b1f2569e8a18ad90c159298d862f96c3964392a20d74fc628aee44/numba-0.61.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:3a10a8fc9afac40b1eac55717cece1b8b1ac0b946f5065c89e00bde646b5b154", size = 2771785, upload-time = "2025-04-09T02:57:59.96Z" }, + { url = "https://files.pythonhosted.org/packages/e9/71/91b277d712e46bd5059f8a5866862ed1116091a7cb03bd2704ba8ebe015f/numba-0.61.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d3bcada3c9afba3bed413fba45845f2fb9cd0d2b27dd58a1be90257e293d140", size = 2773289, upload-time = "2025-04-09T02:58:01.435Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e0/5ea04e7ad2c39288c0f0f9e8d47638ad70f28e275d092733b5817cf243c9/numba-0.61.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdbca73ad81fa196bd53dc12e3aaf1564ae036e0c125f237c7644fe64a4928ab", size = 3893918, upload-time = "2025-04-09T02:58:02.933Z" }, + { url = "https://files.pythonhosted.org/packages/17/58/064f4dcb7d7e9412f16ecf80ed753f92297e39f399c905389688cf950b81/numba-0.61.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f154aaea625fb32cfbe3b80c5456d514d416fcdf79733dd69c0df3a11348e9e", size = 3584056, upload-time = "2025-04-09T02:58:04.538Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/6d3a0f2d3989e62a18749e1e9913d5fa4910bbb3e3311a035baea6caf26d/numba-0.61.2-cp313-cp313-win_amd64.whl", hash = "sha256:59321215e2e0ac5fa928a8020ab00b8e57cda8a97384963ac0dfa4d4e6aa54e7", size = 2831846, upload-time = "2025-04-09T02:58:06.125Z" }, ] [[package]] name = "numpy" version = "2.2.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload_time = "2025-04-19T23:27:42.561Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475, upload_time = "2025-04-19T22:34:24.174Z" }, - { url = "https://files.pythonhosted.org/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474, upload_time = "2025-04-19T22:34:46.578Z" }, - { url = "https://files.pythonhosted.org/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875, upload_time = "2025-04-19T22:34:56.281Z" }, - { url = "https://files.pythonhosted.org/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176, upload_time = "2025-04-19T22:35:07.518Z" }, - { url = "https://files.pythonhosted.org/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850, upload_time = "2025-04-19T22:35:31.347Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306, upload_time = "2025-04-19T22:35:57.573Z" }, - { url = "https://files.pythonhosted.org/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767, upload_time = "2025-04-19T22:36:22.245Z" }, - { url = "https://files.pythonhosted.org/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515, upload_time = "2025-04-19T22:36:49.822Z" }, - { url = "https://files.pythonhosted.org/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842, upload_time = "2025-04-19T22:37:01.624Z" }, - { url = "https://files.pythonhosted.org/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071, upload_time = "2025-04-19T22:37:21.098Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633, upload_time = "2025-04-19T22:37:52.4Z" }, - { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123, upload_time = "2025-04-19T22:38:15.058Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817, upload_time = "2025-04-19T22:38:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066, upload_time = "2025-04-19T22:38:35.782Z" }, - { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277, upload_time = "2025-04-19T22:38:57.697Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742, upload_time = "2025-04-19T22:39:22.689Z" }, - { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825, upload_time = "2025-04-19T22:39:45.794Z" }, - { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600, upload_time = "2025-04-19T22:40:13.427Z" }, - { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626, upload_time = "2025-04-19T22:40:25.223Z" }, - { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715, upload_time = "2025-04-19T22:40:44.528Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload_time = "2025-04-19T22:41:16.234Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload_time = "2025-04-19T22:41:38.472Z" }, - { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload_time = "2025-04-19T22:41:47.823Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload_time = "2025-04-19T22:41:58.689Z" }, - { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload_time = "2025-04-19T22:42:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload_time = "2025-04-19T22:42:44.433Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload_time = "2025-04-19T22:43:09.928Z" }, - { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload_time = "2025-04-19T22:43:36.983Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload_time = "2025-04-19T22:47:10.523Z" }, - { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload_time = "2025-04-19T22:47:30.253Z" }, - { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload_time = "2025-04-19T22:44:09.251Z" }, - { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload_time = "2025-04-19T22:44:31.383Z" }, - { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload_time = "2025-04-19T22:44:40.361Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload_time = "2025-04-19T22:44:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload_time = "2025-04-19T22:45:12.451Z" }, - { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload_time = "2025-04-19T22:45:37.734Z" }, - { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload_time = "2025-04-19T22:46:01.908Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload_time = "2025-04-19T22:46:28.585Z" }, - { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload_time = "2025-04-19T22:46:39.949Z" }, - { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload_time = "2025-04-19T22:47:00.147Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload-time = "2025-04-19T23:27:42.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475, upload-time = "2025-04-19T22:34:24.174Z" }, + { url = "https://files.pythonhosted.org/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474, upload-time = "2025-04-19T22:34:46.578Z" }, + { url = "https://files.pythonhosted.org/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875, upload-time = "2025-04-19T22:34:56.281Z" }, + { url = "https://files.pythonhosted.org/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176, upload-time = "2025-04-19T22:35:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850, upload-time = "2025-04-19T22:35:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306, upload-time = "2025-04-19T22:35:57.573Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767, upload-time = "2025-04-19T22:36:22.245Z" }, + { url = "https://files.pythonhosted.org/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515, upload-time = "2025-04-19T22:36:49.822Z" }, + { url = "https://files.pythonhosted.org/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842, upload-time = "2025-04-19T22:37:01.624Z" }, + { url = "https://files.pythonhosted.org/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071, upload-time = "2025-04-19T22:37:21.098Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633, upload-time = "2025-04-19T22:37:52.4Z" }, + { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123, upload-time = "2025-04-19T22:38:15.058Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817, upload-time = "2025-04-19T22:38:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066, upload-time = "2025-04-19T22:38:35.782Z" }, + { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277, upload-time = "2025-04-19T22:38:57.697Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742, upload-time = "2025-04-19T22:39:22.689Z" }, + { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825, upload-time = "2025-04-19T22:39:45.794Z" }, + { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600, upload-time = "2025-04-19T22:40:13.427Z" }, + { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626, upload-time = "2025-04-19T22:40:25.223Z" }, + { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715, upload-time = "2025-04-19T22:40:44.528Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload-time = "2025-04-19T22:41:16.234Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload-time = "2025-04-19T22:41:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload-time = "2025-04-19T22:41:47.823Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload-time = "2025-04-19T22:41:58.689Z" }, + { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload-time = "2025-04-19T22:42:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload-time = "2025-04-19T22:42:44.433Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload-time = "2025-04-19T22:43:09.928Z" }, + { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload-time = "2025-04-19T22:43:36.983Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload-time = "2025-04-19T22:47:10.523Z" }, + { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload-time = "2025-04-19T22:47:30.253Z" }, + { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload-time = "2025-04-19T22:44:09.251Z" }, + { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload-time = "2025-04-19T22:44:31.383Z" }, + { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload-time = "2025-04-19T22:44:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload-time = "2025-04-19T22:44:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload-time = "2025-04-19T22:45:12.451Z" }, + { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload-time = "2025-04-19T22:45:37.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload-time = "2025-04-19T22:46:01.908Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload-time = "2025-04-19T22:46:28.585Z" }, + { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload-time = "2025-04-19T22:46:39.949Z" }, + { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload-time = "2025-04-19T22:47:00.147Z" }, ] [[package]] @@ -910,95 +910,95 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/3c/9d59b0167458b839273ad0c4fc5f62f787058d8f5aed7f71294963a99471/optype-0.9.3.tar.gz", hash = "sha256:5f09d74127d316053b26971ce441a4df01f3a01943601d3712dd6f34cdfbaf48", size = 96143, upload_time = "2025-03-31T17:00:08.392Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3c/9d59b0167458b839273ad0c4fc5f62f787058d8f5aed7f71294963a99471/optype-0.9.3.tar.gz", hash = "sha256:5f09d74127d316053b26971ce441a4df01f3a01943601d3712dd6f34cdfbaf48", size = 96143, upload-time = "2025-03-31T17:00:08.392Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/d8/ac50e2982bdc2d3595dc2bfe3c7e5a0574b5e407ad82d70b5f3707009671/optype-0.9.3-py3-none-any.whl", hash = "sha256:2935c033265938d66cc4198b0aca865572e635094e60e6e79522852f029d9e8d", size = 84357, upload_time = "2025-03-31T17:00:06.464Z" }, + { url = "https://files.pythonhosted.org/packages/73/d8/ac50e2982bdc2d3595dc2bfe3c7e5a0574b5e407ad82d70b5f3707009671/optype-0.9.3-py3-none-any.whl", hash = "sha256:2935c033265938d66cc4198b0aca865572e635094e60e6e79522852f029d9e8d", size = 84357, upload-time = "2025-03-31T17:00:06.464Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload_time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload_time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] name = "pillow" version = "11.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload_time = "2025-04-12T17:50:03.289Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload_time = "2025-04-12T17:47:37.135Z" }, - { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload_time = "2025-04-12T17:47:39.345Z" }, - { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload_time = "2025-04-12T17:47:41.128Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload_time = "2025-04-12T17:47:42.912Z" }, - { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload_time = "2025-04-12T17:47:44.611Z" }, - { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload_time = "2025-04-12T17:47:46.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload_time = "2025-04-12T17:47:49.255Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload_time = "2025-04-12T17:47:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload_time = "2025-04-12T17:47:54.425Z" }, - { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload_time = "2025-04-12T17:47:56.535Z" }, - { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload_time = "2025-04-12T17:47:58.217Z" }, - { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload_time = "2025-04-12T17:48:00.417Z" }, - { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload_time = "2025-04-12T17:48:02.391Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload_time = "2025-04-12T17:48:04.554Z" }, - { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload_time = "2025-04-12T17:48:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload_time = "2025-04-12T17:48:09.229Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload_time = "2025-04-12T17:48:11.631Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload_time = "2025-04-12T17:48:13.592Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload_time = "2025-04-12T17:48:15.938Z" }, - { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload_time = "2025-04-12T17:48:17.885Z" }, - { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload_time = "2025-04-12T17:48:19.655Z" }, - { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload_time = "2025-04-12T17:48:21.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload_time = "2025-04-12T17:48:23.915Z" }, - { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload_time = "2025-04-12T17:48:25.738Z" }, - { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload_time = "2025-04-12T17:48:27.908Z" }, - { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload_time = "2025-04-12T17:48:29.888Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload_time = "2025-04-12T17:48:31.874Z" }, - { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload_time = "2025-04-12T17:48:34.422Z" }, - { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload_time = "2025-04-12T17:48:37.641Z" }, - { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload_time = "2025-04-12T17:48:39.652Z" }, - { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload_time = "2025-04-12T17:48:41.765Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload_time = "2025-04-12T17:48:43.625Z" }, - { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload_time = "2025-04-12T17:48:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload_time = "2025-04-12T17:48:47.866Z" }, - { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload_time = "2025-04-12T17:48:50.189Z" }, - { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload_time = "2025-04-12T17:48:52.346Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload_time = "2025-04-12T17:48:54.403Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload_time = "2025-04-12T17:48:56.383Z" }, - { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload_time = "2025-04-12T17:48:58.782Z" }, - { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload_time = "2025-04-12T17:49:00.709Z" }, - { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload_time = "2025-04-12T17:49:02.946Z" }, - { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload_time = "2025-04-12T17:49:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload_time = "2025-04-12T17:49:06.635Z" }, - { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload_time = "2025-04-12T17:49:08.399Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload_time = "2025-04-12T17:49:46.789Z" }, - { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload_time = "2025-04-12T17:49:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload_time = "2025-04-12T17:49:50.831Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload_time = "2025-04-12T17:49:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload_time = "2025-04-12T17:49:55.164Z" }, - { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload_time = "2025-04-12T17:49:57.171Z" }, - { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload_time = "2025-04-12T17:49:59.628Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload-time = "2025-04-12T17:47:37.135Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload-time = "2025-04-12T17:47:39.345Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload-time = "2025-04-12T17:47:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload-time = "2025-04-12T17:47:42.912Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload-time = "2025-04-12T17:47:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload-time = "2025-04-12T17:47:46.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload-time = "2025-04-12T17:47:49.255Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload-time = "2025-04-12T17:47:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload-time = "2025-04-12T17:47:54.425Z" }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload-time = "2025-04-12T17:47:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload-time = "2025-04-12T17:47:58.217Z" }, + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload-time = "2025-04-12T17:49:46.789Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload-time = "2025-04-12T17:49:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload-time = "2025-04-12T17:49:50.831Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload-time = "2025-04-12T17:49:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload-time = "2025-04-12T17:49:55.164Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload-time = "2025-04-12T17:49:57.171Z" }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" }, ] [[package]] name = "platformdirs" version = "4.3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload_time = "2025-03-19T20:36:10.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload_time = "2025-03-19T20:36:09.038Z" }, + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload_time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload_time = "2024-04-20T21:34:40.434Z" }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] @@ -1010,9 +1010,9 @@ dependencies = [ { name = "pyyaml" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/9b/fd39836a6397fb363446d83075a7b9c2cc562f4c449292e039ed36084376/pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755", size = 402879, upload_time = "2021-01-17T20:02:27.328Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/9b/fd39836a6397fb363446d83075a7b9c2cc562f4c449292e039ed36084376/pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755", size = 402879, upload-time = "2021-01-17T20:02:27.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/5f/40d8e90f985a05133a8895fc454c6127ecec3de8b095dd35bba91382f803/pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f", size = 561354, upload_time = "2021-01-17T20:02:23.696Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5f/40d8e90f985a05133a8895fc454c6127ecec3de8b095dd35bba91382f803/pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f", size = 561354, upload-time = "2021-01-17T20:02:23.696Z" }, ] [[package]] @@ -1023,18 +1023,18 @@ 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" } +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" }, + { 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 = "pycodestyle" version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload_time = "2025-03-29T17:33:30.669Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload_time = "2025-03-29T17:33:29.405Z" }, + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" }, ] [[package]] @@ -1045,23 +1045,23 @@ dependencies = [ { name = "click" }, { name = "docstring-parser-fork" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/b8/9ab3bb3642e1a87ad5b51276cc6465542935d44c427d75937216b22eadc0/pydoclint-0.6.6.tar.gz", hash = "sha256:22862a8494d05cdf22574d6533f4c47933c0ae1674b0f8b961d6ef42536eaa69", size = 54488, upload_time = "2025-04-16T07:42:23.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/b8/9ab3bb3642e1a87ad5b51276cc6465542935d44c427d75937216b22eadc0/pydoclint-0.6.6.tar.gz", hash = "sha256:22862a8494d05cdf22574d6533f4c47933c0ae1674b0f8b961d6ef42536eaa69", size = 54488, upload-time = "2025-04-16T07:42:23.518Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/05/c714e4dfea5e48140288ee05275e9675fbe1ab1010852ed8c77a388c7ace/pydoclint-0.6.6-py2.py3-none-any.whl", hash = "sha256:7ce8ed36f60f9201bf1c1edacb32c55eb051af80fdd7304480c6419ee0ced43c", size = 48713, upload_time = "2025-04-16T07:42:22.023Z" }, + { url = "https://files.pythonhosted.org/packages/bc/05/c714e4dfea5e48140288ee05275e9675fbe1ab1010852ed8c77a388c7ace/pydoclint-0.6.6-py2.py3-none-any.whl", hash = "sha256:7ce8ed36f60f9201bf1c1edacb32c55eb051af80fdd7304480c6419ee0ced43c", size = 48713, upload-time = "2025-04-16T07:42:22.023Z" }, ] [[package]] name = "pygments" version = "2.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload_time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload_time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] [[package]] name = "pylint" -version = "3.3.6" +version = "3.3.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astroid" }, @@ -1072,9 +1072,9 @@ dependencies = [ { name = "platformdirs" }, { name = "tomlkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/a7/113d02340afb9dcbb0c8b25454e9538cd08f0ebf3e510df4ed916caa1a89/pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a", size = 1519586, upload_time = "2025-03-20T11:25:38.207Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/e4/83e487d3ddd64ab27749b66137b26dc0c5b5c161be680e6beffdc99070b3/pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", size = 1520709, upload-time = "2025-05-04T17:07:51.089Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/21/9537fc94aee9ec7316a230a49895266cf02d78aa29b0a2efbc39566e0935/pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", size = 522462, upload_time = "2025-03-20T11:25:36.13Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/bff755d09e31b5d25cc7fdc4bf3915d1a404e181f1abf0359af376845c24/pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d", size = 522565, upload-time = "2025-05-04T17:07:48.714Z" }, ] [[package]] @@ -1088,18 +1088,18 @@ dependencies = [ { name = "scikit-learn" }, { name = "scipy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/58/560a4db5eb3794d922fe55804b10326534ded3d971e1933c1eef91193f5e/pynndescent-0.5.13.tar.gz", hash = "sha256:d74254c0ee0a1eeec84597d5fe89fedcf778593eeabe32c2f97412934a9800fb", size = 2975955, upload_time = "2024-06-17T15:48:32.914Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/58/560a4db5eb3794d922fe55804b10326534ded3d971e1933c1eef91193f5e/pynndescent-0.5.13.tar.gz", hash = "sha256:d74254c0ee0a1eeec84597d5fe89fedcf778593eeabe32c2f97412934a9800fb", size = 2975955, upload-time = "2024-06-17T15:48:32.914Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/53/d23a97e0a2c690d40b165d1062e2c4ccc796be458a1ce59f6ba030434663/pynndescent-0.5.13-py3-none-any.whl", hash = "sha256:69aabb8f394bc631b6ac475a1c7f3994c54adf3f51cd63b2730fefba5771b949", size = 56850, upload_time = "2024-06-17T15:48:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/d23a97e0a2c690d40b165d1062e2c4ccc796be458a1ce59f6ba030434663/pynndescent-0.5.13-py3-none-any.whl", hash = "sha256:69aabb8f394bc631b6ac475a1c7f3994c54adf3f51cd63b2730fefba5771b949", size = 56850, upload-time = "2024-06-17T15:48:31.184Z" }, ] [[package]] name = "pyparsing" version = "3.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload_time = "2025-03-25T05:01:28.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload_time = "2025-03-25T05:01:24.908Z" }, + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, ] [[package]] @@ -1109,9 +1109,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714, upload_time = "2025-01-21T18:02:00.923Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714, upload-time = "2025-01-21T18:02:00.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131, upload_time = "2025-01-21T18:01:58.927Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131, upload-time = "2025-01-21T18:01:58.927Z" }, ] [[package]] @@ -1124,9 +1124,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload_time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload_time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] @@ -1137,9 +1137,9 @@ dependencies = [ { name = "coverage" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload_time = "2025-04-05T14:07:51.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload_time = "2025-04-05T14:07:49.641Z" }, + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, ] [[package]] @@ -1151,9 +1151,9 @@ dependencies = [ { name = "mypy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/50/3ce149b469e27848c1dc354553b17774f9dde0140625f5a4130bd21e1052/pytest_mypy-1.0.1.tar.gz", hash = "sha256:3f5fcaff75c80dccc6b68cf5ecc28e1bbe71e95309469eb7a28bf408ce55c074", size = 15975, upload_time = "2025-04-02T19:31:16.151Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/50/3ce149b469e27848c1dc354553b17774f9dde0140625f5a4130bd21e1052/pytest_mypy-1.0.1.tar.gz", hash = "sha256:3f5fcaff75c80dccc6b68cf5ecc28e1bbe71e95309469eb7a28bf408ce55c074", size = 15975, upload-time = "2025-04-02T19:31:16.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/93/25ed3c02e15c4ef1b04cbda7c708ffc5da755986aaacfb48db1f9e84a996/pytest_mypy-1.0.1-py3-none-any.whl", hash = "sha256:ad7133c9b92c802e032f2596590ebede7eea7c418e61d60d5cdd571b55c72056", size = 8701, upload_time = "2025-04-02T19:31:14.914Z" }, + { url = "https://files.pythonhosted.org/packages/bf/93/25ed3c02e15c4ef1b04cbda7c708ffc5da755986aaacfb48db1f9e84a996/pytest_mypy-1.0.1-py3-none-any.whl", hash = "sha256:ad7133c9b92c802e032f2596590ebede7eea7c418e61d60d5cdd571b55c72056", size = 8701, upload-time = "2025-04-02T19:31:14.914Z" }, ] [[package]] @@ -1163,44 +1163,44 @@ 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" } +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" }, + { 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 = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload_time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload_time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload_time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload_time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload_time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload_time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload_time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload_time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload_time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload_time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload_time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload_time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload_time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload_time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload_time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload_time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload_time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload_time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload_time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload_time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload_time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload_time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload_time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload_time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload_time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload_time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload_time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload_time = "2024-08-06T20:33:04.33Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] [[package]] @@ -1213,18 +1213,18 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, ] [[package]] name = "roman-numerals-py" version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload_time = "2025-02-22T07:34:54.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload_time = "2025-02-22T07:34:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, ] [[package]] @@ -1241,24 +1241,24 @@ dependencies = [ { name = "scipy" }, { name = "tifffile" }, ] -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" } +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/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" }, + { 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]] @@ -1271,27 +1271,27 @@ dependencies = [ { name = "scipy" }, { name = "threadpoolctl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312, upload_time = "2025-01-10T08:07:55.348Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620, upload_time = "2025-01-10T08:06:16.675Z" }, - { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234, upload_time = "2025-01-10T08:06:21.83Z" }, - { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155, upload_time = "2025-01-10T08:06:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069, upload_time = "2025-01-10T08:06:32.515Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809, upload_time = "2025-01-10T08:06:35.514Z" }, - { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516, upload_time = "2025-01-10T08:06:40.009Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837, upload_time = "2025-01-10T08:06:43.305Z" }, - { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728, upload_time = "2025-01-10T08:06:47.618Z" }, - { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700, upload_time = "2025-01-10T08:06:50.888Z" }, - { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613, upload_time = "2025-01-10T08:06:54.115Z" }, - { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001, upload_time = "2025-01-10T08:06:58.613Z" }, - { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360, upload_time = "2025-01-10T08:07:01.556Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004, upload_time = "2025-01-10T08:07:06.931Z" }, - { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776, upload_time = "2025-01-10T08:07:11.715Z" }, - { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865, upload_time = "2025-01-10T08:07:16.088Z" }, - { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804, upload_time = "2025-01-10T08:07:20.385Z" }, - { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530, upload_time = "2025-01-10T08:07:23.675Z" }, - { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852, upload_time = "2025-01-10T08:07:26.817Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256, upload_time = "2025-01-10T08:07:31.084Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312, upload-time = "2025-01-10T08:07:55.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620, upload-time = "2025-01-10T08:06:16.675Z" }, + { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234, upload-time = "2025-01-10T08:06:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155, upload-time = "2025-01-10T08:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069, upload-time = "2025-01-10T08:06:32.515Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809, upload-time = "2025-01-10T08:06:35.514Z" }, + { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516, upload-time = "2025-01-10T08:06:40.009Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837, upload-time = "2025-01-10T08:06:43.305Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728, upload-time = "2025-01-10T08:06:47.618Z" }, + { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700, upload-time = "2025-01-10T08:06:50.888Z" }, + { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613, upload-time = "2025-01-10T08:06:54.115Z" }, + { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001, upload-time = "2025-01-10T08:06:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360, upload-time = "2025-01-10T08:07:01.556Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004, upload-time = "2025-01-10T08:07:06.931Z" }, + { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776, upload-time = "2025-01-10T08:07:11.715Z" }, + { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865, upload-time = "2025-01-10T08:07:16.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804, upload-time = "2025-01-10T08:07:20.385Z" }, + { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530, upload-time = "2025-01-10T08:07:23.675Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852, upload-time = "2025-01-10T08:07:26.817Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256, upload-time = "2025-01-10T08:07:31.084Z" }, ] [[package]] @@ -1301,83 +1301,83 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316, upload_time = "2025-02-17T00:42:24.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651, upload_time = "2025-02-17T00:30:31.09Z" }, - { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038, upload_time = "2025-02-17T00:30:40.219Z" }, - { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518, upload_time = "2025-02-17T00:30:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523, upload_time = "2025-02-17T00:30:56.002Z" }, - { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547, upload_time = "2025-02-17T00:31:07.599Z" }, - { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077, upload_time = "2025-02-17T00:31:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657, upload_time = "2025-02-17T00:31:22.041Z" }, - { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857, upload_time = "2025-02-17T00:31:29.836Z" }, - { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654, upload_time = "2025-02-17T00:31:43.65Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184, upload_time = "2025-02-17T00:31:50.623Z" }, - { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558, upload_time = "2025-02-17T00:31:56.721Z" }, - { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211, upload_time = "2025-02-17T00:32:03.042Z" }, - { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260, upload_time = "2025-02-17T00:32:07.847Z" }, - { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095, upload_time = "2025-02-17T00:32:14.565Z" }, - { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371, upload_time = "2025-02-17T00:32:21.411Z" }, - { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390, upload_time = "2025-02-17T00:32:29.421Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276, upload_time = "2025-02-17T00:32:37.431Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317, upload_time = "2025-02-17T00:32:45.47Z" }, - { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587, upload_time = "2025-02-17T00:32:53.196Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266, upload_time = "2025-02-17T00:32:59.318Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768, upload_time = "2025-02-17T00:33:04.091Z" }, - { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719, upload_time = "2025-02-17T00:33:08.909Z" }, - { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195, upload_time = "2025-02-17T00:33:15.352Z" }, - { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404, upload_time = "2025-02-17T00:33:22.21Z" }, - { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011, upload_time = "2025-02-17T00:33:29.446Z" }, - { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406, upload_time = "2025-02-17T00:33:39.019Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243, upload_time = "2025-02-17T00:34:51.024Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286, upload_time = "2025-02-17T00:33:47.62Z" }, - { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634, upload_time = "2025-02-17T00:33:54.131Z" }, - { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179, upload_time = "2025-02-17T00:33:59.948Z" }, - { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412, upload_time = "2025-02-17T00:34:06.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867, upload_time = "2025-02-17T00:34:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009, upload_time = "2025-02-17T00:34:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159, upload_time = "2025-02-17T00:34:26.724Z" }, - { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566, upload_time = "2025-02-17T00:34:34.512Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705, upload_time = "2025-02-17T00:34:43.619Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316, upload-time = "2025-02-17T00:42:24.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651, upload-time = "2025-02-17T00:30:31.09Z" }, + { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038, upload-time = "2025-02-17T00:30:40.219Z" }, + { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518, upload-time = "2025-02-17T00:30:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523, upload-time = "2025-02-17T00:30:56.002Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547, upload-time = "2025-02-17T00:31:07.599Z" }, + { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077, upload-time = "2025-02-17T00:31:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657, upload-time = "2025-02-17T00:31:22.041Z" }, + { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857, upload-time = "2025-02-17T00:31:29.836Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654, upload-time = "2025-02-17T00:31:43.65Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184, upload-time = "2025-02-17T00:31:50.623Z" }, + { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558, upload-time = "2025-02-17T00:31:56.721Z" }, + { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211, upload-time = "2025-02-17T00:32:03.042Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260, upload-time = "2025-02-17T00:32:07.847Z" }, + { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095, upload-time = "2025-02-17T00:32:14.565Z" }, + { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371, upload-time = "2025-02-17T00:32:21.411Z" }, + { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390, upload-time = "2025-02-17T00:32:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276, upload-time = "2025-02-17T00:32:37.431Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317, upload-time = "2025-02-17T00:32:45.47Z" }, + { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587, upload-time = "2025-02-17T00:32:53.196Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266, upload-time = "2025-02-17T00:32:59.318Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768, upload-time = "2025-02-17T00:33:04.091Z" }, + { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719, upload-time = "2025-02-17T00:33:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195, upload-time = "2025-02-17T00:33:15.352Z" }, + { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404, upload-time = "2025-02-17T00:33:22.21Z" }, + { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011, upload-time = "2025-02-17T00:33:29.446Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406, upload-time = "2025-02-17T00:33:39.019Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243, upload-time = "2025-02-17T00:34:51.024Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286, upload-time = "2025-02-17T00:33:47.62Z" }, + { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634, upload-time = "2025-02-17T00:33:54.131Z" }, + { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179, upload-time = "2025-02-17T00:33:59.948Z" }, + { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412, upload-time = "2025-02-17T00:34:06.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867, upload-time = "2025-02-17T00:34:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009, upload-time = "2025-02-17T00:34:19.55Z" }, + { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159, upload-time = "2025-02-17T00:34:26.724Z" }, + { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566, upload-time = "2025-02-17T00:34:34.512Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705, upload-time = "2025-02-17T00:34:43.619Z" }, ] [[package]] name = "scipy-stubs" -version = "1.15.2.2" +version = "1.15.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "optype" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/ee/0c3e93545b53d3b22e662fbe251c3e61c2b14742f296f36708eff4a2898c/scipy_stubs-1.15.2.2.tar.gz", hash = "sha256:0137d907d75381d2eda4f6af5b1d3211759cb193a0eadf5195716fb0b01ca3cb", size = 275755, upload_time = "2025-04-07T20:59:18.482Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/35c43bd7d412add4adcd68475702571b2489b50c40b6564f808b2355e452/scipy_stubs-1.15.3.0.tar.gz", hash = "sha256:e8f76c9887461cf9424c1e2ad78ea5dac71dd4cbb383dc85f91adfe8f74d1e17", size = 275699, upload-time = "2025-05-08T16:58:35.139Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/1a/3eba813584e398d589e1d4e0dac0cf822ce9e25b28cb2d1f0012d137c968/scipy_stubs-1.15.2.2-py3-none-any.whl", hash = "sha256:f02fe66124b58bce5f0897ecd48d0e79226a999cc4e6984a9472520c20b8e4b6", size = 459133, upload_time = "2025-04-07T20:59:16.664Z" }, + { url = "https://files.pythonhosted.org/packages/6c/42/cd8dc81f8060de1f14960885ad5b2d2651f41de8b93d09f3f919d6567a5a/scipy_stubs-1.15.3.0-py3-none-any.whl", hash = "sha256:a251254cf4fd6e7fb87c55c1feee92d32ddbc1f542ecdf6a0159cdb81c2fb62d", size = 459062, upload-time = "2025-05-08T16:58:33.356Z" }, ] [[package]] name = "setuptools" version = "78.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827, upload_time = "2025-03-25T22:49:35.332Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827, upload-time = "2025-03-25T22:49:35.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108, upload_time = "2025-03-25T22:49:33.13Z" }, + { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108, upload-time = "2025-03-25T22:49:33.13Z" }, ] [[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" } +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" }, + { 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 = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699, upload_time = "2021-11-16T18:38:38.009Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699, upload-time = "2021-11-16T18:38:38.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002, upload_time = "2021-11-16T18:38:34.792Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002, upload-time = "2021-11-16T18:38:34.792Z" }, ] [[package]] @@ -1403,9 +1403,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -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" } +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" }, + { 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]] @@ -1415,9 +1415,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload_time = "2023-04-14T08:10:22.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload_time = "2023-04-14T08:10:20.844Z" }, + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] [[package]] @@ -1429,18 +1429,18 @@ dependencies = [ { name = "sphinx" }, { 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" } +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" }, + { 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" } +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" }, + { 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]] @@ -1454,9 +1454,9 @@ dependencies = [ { name = "setuptools", marker = "python_full_version >= '3.12'" }, { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/ce/054a8ec04063f9a27772fea7188f796edbfa382e656d3b76428323861f0e/sphinxcontrib_bibtex-2.6.3.tar.gz", hash = "sha256:7c790347ef1cb0edf30de55fc324d9782d085e89c52c2b8faafa082e08e23946", size = 117177, upload_time = "2024-09-12T14:23:44.662Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/ce/054a8ec04063f9a27772fea7188f796edbfa382e656d3b76428323861f0e/sphinxcontrib_bibtex-2.6.3.tar.gz", hash = "sha256:7c790347ef1cb0edf30de55fc324d9782d085e89c52c2b8faafa082e08e23946", size = 117177, upload-time = "2024-09-12T14:23:44.662Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/49/c23f9493c0a5d5881fb7ed3002e87708454fef860aa96a48e755d27bf6f0/sphinxcontrib_bibtex-2.6.3-py3-none-any.whl", hash = "sha256:ff016b738fcc867df0f75c29e139b3b2158d26a2c802db27963cb128be3b75fb", size = 40340, upload_time = "2024-09-12T14:23:43.593Z" }, + { url = "https://files.pythonhosted.org/packages/8e/49/c23f9493c0a5d5881fb7ed3002e87708454fef860aa96a48e755d27bf6f0/sphinxcontrib_bibtex-2.6.3-py3-none-any.whl", hash = "sha256:ff016b738fcc867df0f75c29e139b3b2158d26a2c802db27963cb128be3b75fb", size = 40340, upload-time = "2024-09-12T14:23:43.593Z" }, ] [[package]] @@ -1469,27 +1469,27 @@ dependencies = [ { name = "sphinx" }, { name = "sphinxcontrib-runcmd" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/9e/8ac54a6a3e7a00339f417568899b64a0c0d622429db73cc1a28c8122c8e2/sphinxcontrib.datatemplates-0.11.0.tar.gz", hash = "sha256:793222e803430076341509cc167f8d715830b05e418c885313101d60fd442557", size = 30996, upload_time = "2023-12-21T14:59:10.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/9e/8ac54a6a3e7a00339f417568899b64a0c0d622429db73cc1a28c8122c8e2/sphinxcontrib.datatemplates-0.11.0.tar.gz", hash = "sha256:793222e803430076341509cc167f8d715830b05e418c885313101d60fd442557", size = 30996, upload-time = "2023-12-21T14:59:10.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/8d/7a7dd95ad1eedec8dc770570c8b1f3dc1d13357383635607b6629ccf329c/sphinxcontrib.datatemplates-0.11.0-py3-none-any.whl", hash = "sha256:88d02f5edab32b88211ebb72a90553e3676a5737877bad1de412f84058ac282e", size = 12493, upload_time = "2023-12-21T14:59:07.96Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8d/7a7dd95ad1eedec8dc770570c8b1f3dc1d13357383635607b6629ccf329c/sphinxcontrib.datatemplates-0.11.0-py3-none-any.whl", hash = "sha256:88d02f5edab32b88211ebb72a90553e3676a5737877bad1de412f84058ac282e", size = 12493, upload-time = "2023-12-21T14:59:07.96Z" }, ] [[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" } +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" }, + { 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" } +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" }, + { 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]] @@ -1499,27 +1499,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -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" } +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" }, + { 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" } +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" }, + { 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" } +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" }, + { 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]] @@ -1529,27 +1529,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/03/6eb30814c9839f36131284a46ec9fc39d7bd356078648bc7125d5d1c05e8/sphinxcontrib-runcmd-0.2.0.tar.gz", hash = "sha256:3551c389d9c5fe82d693c7222feb9658b1a1a5a1abcb0063e8385e5528c64c76", size = 5376, upload_time = "2018-11-08T20:19:34.32Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/03/6eb30814c9839f36131284a46ec9fc39d7bd356078648bc7125d5d1c05e8/sphinxcontrib-runcmd-0.2.0.tar.gz", hash = "sha256:3551c389d9c5fe82d693c7222feb9658b1a1a5a1abcb0063e8385e5528c64c76", size = 5376, upload-time = "2018-11-08T20:19:34.32Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/d9/67a79080b5d9fcb367470af9e525a9c53122e95744665de09462dcd676d8/sphinxcontrib_runcmd-0.2.0-py2.py3-none-any.whl", hash = "sha256:7b739b68e27210b4c7c12ba16e5b3da7b313c49991401f896d29bea0f0771934", size = 6043, upload_time = "2018-11-08T20:19:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/67a79080b5d9fcb367470af9e525a9c53122e95744665de09462dcd676d8/sphinxcontrib_runcmd-0.2.0-py2.py3-none-any.whl", hash = "sha256:7b739b68e27210b4c7c12ba16e5b3da7b313c49991401f896d29bea0f0771934", size = 6043, upload-time = "2018-11-08T20:19:33.17Z" }, ] [[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" } +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" }, + { 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 = "threadpoolctl" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload_time = "2025-03-13T13:49:23.031Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload_time = "2025-03-13T13:49:21.846Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, ] [[package]] @@ -1559,23 +1559,23 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/54/d5ebe66a9de349b833e570e87bdbd9eec76ec54bd505c24b0591a15783ad/tifffile-2025.3.30.tar.gz", hash = "sha256:3cdee47fe06cd75367c16bc3ff34523713156dae6cd498e3a392e5b39a51b789", size = 366039, upload_time = "2025-03-30T04:45:30.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/54/d5ebe66a9de349b833e570e87bdbd9eec76ec54bd505c24b0591a15783ad/tifffile-2025.3.30.tar.gz", hash = "sha256:3cdee47fe06cd75367c16bc3ff34523713156dae6cd498e3a392e5b39a51b789", size = 366039, upload-time = "2025-03-30T04:45:30.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/be/10d23cfd4078fbec6aba768a357eff9e70c0b6d2a07398425985c524ad2a/tifffile-2025.3.30-py3-none-any.whl", hash = "sha256:0ed6eee7b66771db2d1bfc42262a51b01887505d35539daef118f4ff8c0f629c", size = 226837, upload_time = "2025-03-30T04:45:29Z" }, + { url = "https://files.pythonhosted.org/packages/6e/be/10d23cfd4078fbec6aba768a357eff9e70c0b6d2a07398425985c524ad2a/tifffile-2025.3.30-py3-none-any.whl", hash = "sha256:0ed6eee7b66771db2d1bfc42262a51b01887505d35539daef118f4ff8c0f629c", size = 226837, upload-time = "2025-03-30T04:45:29Z" }, ] [[package]] name = "tomlkit" version = "0.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885, upload_time = "2024-08-14T08:19:41.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885, upload-time = "2024-08-14T08:19:41.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955, upload_time = "2024-08-14T08:19:40.05Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955, upload-time = "2024-08-14T08:19:40.05Z" }, ] [[package]] name = "tox" -version = "4.25.0" +version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, @@ -1588,9 +1588,9 @@ dependencies = [ { name = "pyproject-api" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/87/692478f0a194f1cad64803692642bd88c12c5b64eee16bf178e4a32e979c/tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52", size = 196255, upload_time = "2025-03-27T15:13:37.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3c/dcec0c00321a107f7f697fd00754c5112572ea6dcacb40b16d8c3eea7c37/tox-4.26.0.tar.gz", hash = "sha256:a83b3b67b0159fa58e44e646505079e35a43317a62d2ae94725e0586266faeca", size = 197260, upload-time = "2025-05-13T15:04:28.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/38/33348de6fc4b1afb3d76d8485c8aecbdabcfb3af8da53d40c792332e2b37/tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c", size = 172420, upload_time = "2025-03-27T15:13:35.703Z" }, + { url = "https://files.pythonhosted.org/packages/de/14/f58b4087cf248b18c795b5c838c7a8d1428dfb07cb468dad3ec7f54041ab/tox-4.26.0-py3-none-any.whl", hash = "sha256:75f17aaf09face9b97bd41645028d9f722301e912be8b4c65a3f938024560224", size = 172761, upload-time = "2025-05-13T15:04:26.207Z" }, ] [[package]] @@ -1602,9 +1602,9 @@ dependencies = [ { name = "tox" }, { name = "uv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/3a/3e445f25978a716ba6674f33f687d9336d0312086a277a778a5e9e9220d7/tox_uv-1.25.0.tar.gz", hash = "sha256:59ee5e694c41fef7bbcf058f22a5f9b6a8509698def2ea60c08554f4e36b9fcc", size = 21114, upload_time = "2025-02-21T16:37:51.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/3a/3e445f25978a716ba6674f33f687d9336d0312086a277a778a5e9e9220d7/tox_uv-1.25.0.tar.gz", hash = "sha256:59ee5e694c41fef7bbcf058f22a5f9b6a8509698def2ea60c08554f4e36b9fcc", size = 21114, upload-time = "2025-02-21T16:37:51.796Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a7/f5c29e0e6faaccefcab607f672b176927144e9412c8183d21301ea2a6f6c/tox_uv-1.25.0-py3-none-any.whl", hash = "sha256:50cfe7795dcd49b2160d7d65b5ece8717f38cfedc242c852a40ec0a71e159bf7", size = 16431, upload_time = "2025-02-21T16:37:49.657Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a7/f5c29e0e6faaccefcab607f672b176927144e9412c8183d21301ea2a6f6c/tox_uv-1.25.0-py3-none-any.whl", hash = "sha256:50cfe7795dcd49b2160d7d65b5ece8717f38cfedc242c852a40ec0a71e159bf7", size = 16431, upload-time = "2025-02-21T16:37:49.657Z" }, ] [[package]] @@ -1614,18 +1614,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload_time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload_time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] [[package]] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload_time = "2025-04-10T14:19:05.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload_time = "2025-04-10T14:19:03.967Z" }, + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] @@ -1640,55 +1640,55 @@ dependencies = [ { name = "scipy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/d4/9ed627905f7993349671283b3c5bf2d9f543ef79229fa1c7e01324eb900c/umap-learn-0.5.7.tar.gz", hash = "sha256:b2a97973e4c6ffcebf241100a8de589a4c84126a832ab40f296c6d9fcc5eb19e", size = 92680, upload_time = "2024-10-28T18:05:57.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/d4/9ed627905f7993349671283b3c5bf2d9f543ef79229fa1c7e01324eb900c/umap-learn-0.5.7.tar.gz", hash = "sha256:b2a97973e4c6ffcebf241100a8de589a4c84126a832ab40f296c6d9fcc5eb19e", size = 92680, upload-time = "2024-10-28T18:05:57.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/8f/671c0e1f2572ba625cbcc1faeba9435e00330c3d6962858711445cf1e817/umap_learn-0.5.7-py3-none-any.whl", hash = "sha256:6a7e0be2facfa365a5ed6588447102bdbef32a0ef449535c25c97ea7e680073c", size = 88815, upload_time = "2024-10-28T18:05:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8f/671c0e1f2572ba625cbcc1faeba9435e00330c3d6962858711445cf1e817/umap_learn-0.5.7-py3-none-any.whl", hash = "sha256:6a7e0be2facfa365a5ed6588447102bdbef32a0ef449535c25c97ea7e680073c", size = 88815, upload-time = "2024-10-28T18:05:55.333Z" }, ] [[package]] name = "urllib3" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, ] [[package]] name = "uv" version = "0.6.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/eb/07bc000a3c05372448b63c45da98630c532ec4e059d848488c3e774d017a/uv-0.6.14.tar.gz", hash = "sha256:a117466f307d164a74444949cc94ec4328ec880fb489cbaa7df324dab14c5c98", size = 3134567, upload_time = "2025-04-09T21:57:01.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/eb/07bc000a3c05372448b63c45da98630c532ec4e059d848488c3e774d017a/uv-0.6.14.tar.gz", hash = "sha256:a117466f307d164a74444949cc94ec4328ec880fb489cbaa7df324dab14c5c98", size = 3134567, upload-time = "2025-04-09T21:57:01.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/bf/3e87dec7728b249458967f39a301376cb776e559c90261c1dac963686dc3/uv-0.6.14-py3-none-linux_armv6l.whl", hash = "sha256:c775e5d7a80ff43cb88856bbdcd838918d5ac3dc362414317e6bbaeb615fff98", size = 16228143, upload_time = "2025-04-09T21:56:03.883Z" }, - { url = "https://files.pythonhosted.org/packages/24/b2/111e1ea40453d93c849f36a67397b51d9b458e6e598c3629ffe76d11b490/uv-0.6.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2578f6f8cdbcc036ffad1043f9f66ade3ac0babf29def6abd9eefd4a7c6621cb", size = 16273279, upload_time = "2025-04-09T21:56:08.311Z" }, - { url = "https://files.pythonhosted.org/packages/72/89/e7fc8a047f08234cc26d1e37e5f573887744205d087f8e8e6f3d0feb04ce/uv-0.6.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9fc8fe58871b4fe02a863b05b8b1b25ef1b6c60d4d224e85338f5c2be0ab4f0e", size = 15115451, upload_time = "2025-04-09T21:56:12.061Z" }, - { url = "https://files.pythonhosted.org/packages/20/1e/72ac3d1e0805d3b49b0a4de46483489ea1989827440f42b0cfb444cdc67f/uv-0.6.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2fb2cd7f6aae21b81474b0051d30e7ed939a9a71714948c47f58b0e7acdd2a80", size = 15540456, upload_time = "2025-04-09T21:56:14.971Z" }, - { url = "https://files.pythonhosted.org/packages/fd/47/5aeb7fb80c673bc28ccf3ab99e376b1cd92eac41af6b9b48c0e38b114c54/uv-0.6.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d6ca3f99c1a6c1c430ae8f451133fb4e8c3a22f661c257425402a5d9430bb797", size = 15979820, upload_time = "2025-04-09T21:56:17.949Z" }, - { url = "https://files.pythonhosted.org/packages/1f/44/c3ad856473f2ef5f22c865a73a0a37ee82d11fcca78ae82f5ac895a7023a/uv-0.6.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed41877b679e0a1af9ab65427d829b87a81b499017e59c70756d4ba02ca43fcb", size = 16650494, upload_time = "2025-04-09T21:56:21.403Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f6/8a1245530c282d470909db78cf56831693c58b90d9b819e35aa2d85fbbe8/uv-0.6.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe9b4361b1c8055301b715fdd94d94eb512053dc4545fec40d3fe3657f655987", size = 17505028, upload_time = "2025-04-09T21:56:24.749Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/0806268440651e2ad1b3542af42b800e20bb7e43050a9ca78f3d1eb4c660/uv-0.6.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998b67bb1cebbe044fc2c5cb251c29cffc56f62a6d55719d6f4e960461d6edad", size = 17245854, upload_time = "2025-04-09T21:56:28.052Z" }, - { url = "https://files.pythonhosted.org/packages/2a/3a/0da9780868626466d8c4977fb02d1b0daa80e6f7504d7b662cae3fb4af3d/uv-0.6.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d433925db6e2ef46047b68962d136ff2ef17a7b5609168615f19e60674232c9", size = 21584756, upload_time = "2025-04-09T21:56:31.886Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fd/21a82b78173be1a2ea20f4f55154e7252bd80d21ed60b9bbbc0e2047b8d0/uv-0.6.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36aaeb00a70a10f748e16c7a1fc410862e2ba905806e7e9dfbc3e64596309404", size = 16878847, upload_time = "2025-04-09T21:56:35.628Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9a/7c84650ae9fb801ecc848d49dcba201243989d9234fe3ec4a4e935ff21c0/uv-0.6.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:11779beb3bd1f92814bc8d8cd350d5228e8f9198cca2f52138b53030a4061d93", size = 15810089, upload_time = "2025-04-09T21:56:39.175Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b3/efcbd3a2d298801109b24feee655bb80fe4178aa6bf68e49664c48b342b2/uv-0.6.14-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:bf1ec103cf9a0850f03935dc6a93cacc680fa2c90c3b41cfc10da311afab8f5b", size = 15962056, upload_time = "2025-04-09T21:56:42.236Z" }, - { url = "https://files.pythonhosted.org/packages/3f/53/c92c894cb34e9578c2e6dc195bcd4eb0a140dd57c96a60207d847521a902/uv-0.6.14-py3-none-musllinux_1_1_i686.whl", hash = "sha256:955e36c98a438a249e178988d4f13b1bb831eb57264d73c459f171b5afd7b023", size = 16255226, upload_time = "2025-04-09T21:56:45.402Z" }, - { url = "https://files.pythonhosted.org/packages/df/eb/38bc37856691d53008bf094d03d9e7ab0c2927523a3901c83e152e7c9915/uv-0.6.14-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2d534e7dc1299c8b53eb7b4c7575e4f0933673ea8b1275d3f3022f5670e311db", size = 17005225, upload_time = "2025-04-09T21:56:48.609Z" }, - { url = "https://files.pythonhosted.org/packages/d8/fe/087d5193603e16bc5f67556d94cf8fa8634785c5863cccdec825f14e9a4c/uv-0.6.14-py3-none-win32.whl", hash = "sha256:7cdf3c8d927b07d4eaffc44809eb57523d449705f10dabbdd6f34f7bdfc7d5fe", size = 16131231, upload_time = "2025-04-09T21:56:52.117Z" }, - { url = "https://files.pythonhosted.org/packages/40/17/33c5c1503c35c874932d4a21ec10a55051e3695dba12b7de700bcfad0cca/uv-0.6.14-py3-none-win_amd64.whl", hash = "sha256:012f46bef6909209c4a6749e4019eb755ba762d37d7ceaaf76da9cb4b7f771e9", size = 17628508, upload_time = "2025-04-09T21:56:55.444Z" }, - { url = "https://files.pythonhosted.org/packages/77/09/163062d439ddc0d89e527ae0e631abf1f7781b183442d8823c48af368f5d/uv-0.6.14-py3-none-win_arm64.whl", hash = "sha256:7465081b4d0b213d0055ccb48de7fe546b5cf0853c6d3601115760760634f6d8", size = 16387232, upload_time = "2025-04-09T21:56:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bf/3e87dec7728b249458967f39a301376cb776e559c90261c1dac963686dc3/uv-0.6.14-py3-none-linux_armv6l.whl", hash = "sha256:c775e5d7a80ff43cb88856bbdcd838918d5ac3dc362414317e6bbaeb615fff98", size = 16228143, upload-time = "2025-04-09T21:56:03.883Z" }, + { url = "https://files.pythonhosted.org/packages/24/b2/111e1ea40453d93c849f36a67397b51d9b458e6e598c3629ffe76d11b490/uv-0.6.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2578f6f8cdbcc036ffad1043f9f66ade3ac0babf29def6abd9eefd4a7c6621cb", size = 16273279, upload-time = "2025-04-09T21:56:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/72/89/e7fc8a047f08234cc26d1e37e5f573887744205d087f8e8e6f3d0feb04ce/uv-0.6.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9fc8fe58871b4fe02a863b05b8b1b25ef1b6c60d4d224e85338f5c2be0ab4f0e", size = 15115451, upload-time = "2025-04-09T21:56:12.061Z" }, + { url = "https://files.pythonhosted.org/packages/20/1e/72ac3d1e0805d3b49b0a4de46483489ea1989827440f42b0cfb444cdc67f/uv-0.6.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2fb2cd7f6aae21b81474b0051d30e7ed939a9a71714948c47f58b0e7acdd2a80", size = 15540456, upload-time = "2025-04-09T21:56:14.971Z" }, + { url = "https://files.pythonhosted.org/packages/fd/47/5aeb7fb80c673bc28ccf3ab99e376b1cd92eac41af6b9b48c0e38b114c54/uv-0.6.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d6ca3f99c1a6c1c430ae8f451133fb4e8c3a22f661c257425402a5d9430bb797", size = 15979820, upload-time = "2025-04-09T21:56:17.949Z" }, + { url = "https://files.pythonhosted.org/packages/1f/44/c3ad856473f2ef5f22c865a73a0a37ee82d11fcca78ae82f5ac895a7023a/uv-0.6.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed41877b679e0a1af9ab65427d829b87a81b499017e59c70756d4ba02ca43fcb", size = 16650494, upload-time = "2025-04-09T21:56:21.403Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f6/8a1245530c282d470909db78cf56831693c58b90d9b819e35aa2d85fbbe8/uv-0.6.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe9b4361b1c8055301b715fdd94d94eb512053dc4545fec40d3fe3657f655987", size = 17505028, upload-time = "2025-04-09T21:56:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/0806268440651e2ad1b3542af42b800e20bb7e43050a9ca78f3d1eb4c660/uv-0.6.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998b67bb1cebbe044fc2c5cb251c29cffc56f62a6d55719d6f4e960461d6edad", size = 17245854, upload-time = "2025-04-09T21:56:28.052Z" }, + { url = "https://files.pythonhosted.org/packages/2a/3a/0da9780868626466d8c4977fb02d1b0daa80e6f7504d7b662cae3fb4af3d/uv-0.6.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d433925db6e2ef46047b68962d136ff2ef17a7b5609168615f19e60674232c9", size = 21584756, upload-time = "2025-04-09T21:56:31.886Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/21a82b78173be1a2ea20f4f55154e7252bd80d21ed60b9bbbc0e2047b8d0/uv-0.6.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36aaeb00a70a10f748e16c7a1fc410862e2ba905806e7e9dfbc3e64596309404", size = 16878847, upload-time = "2025-04-09T21:56:35.628Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9a/7c84650ae9fb801ecc848d49dcba201243989d9234fe3ec4a4e935ff21c0/uv-0.6.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:11779beb3bd1f92814bc8d8cd350d5228e8f9198cca2f52138b53030a4061d93", size = 15810089, upload-time = "2025-04-09T21:56:39.175Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/efcbd3a2d298801109b24feee655bb80fe4178aa6bf68e49664c48b342b2/uv-0.6.14-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:bf1ec103cf9a0850f03935dc6a93cacc680fa2c90c3b41cfc10da311afab8f5b", size = 15962056, upload-time = "2025-04-09T21:56:42.236Z" }, + { url = "https://files.pythonhosted.org/packages/3f/53/c92c894cb34e9578c2e6dc195bcd4eb0a140dd57c96a60207d847521a902/uv-0.6.14-py3-none-musllinux_1_1_i686.whl", hash = "sha256:955e36c98a438a249e178988d4f13b1bb831eb57264d73c459f171b5afd7b023", size = 16255226, upload-time = "2025-04-09T21:56:45.402Z" }, + { url = "https://files.pythonhosted.org/packages/df/eb/38bc37856691d53008bf094d03d9e7ab0c2927523a3901c83e152e7c9915/uv-0.6.14-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2d534e7dc1299c8b53eb7b4c7575e4f0933673ea8b1275d3f3022f5670e311db", size = 17005225, upload-time = "2025-04-09T21:56:48.609Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fe/087d5193603e16bc5f67556d94cf8fa8634785c5863cccdec825f14e9a4c/uv-0.6.14-py3-none-win32.whl", hash = "sha256:7cdf3c8d927b07d4eaffc44809eb57523d449705f10dabbdd6f34f7bdfc7d5fe", size = 16131231, upload-time = "2025-04-09T21:56:52.117Z" }, + { url = "https://files.pythonhosted.org/packages/40/17/33c5c1503c35c874932d4a21ec10a55051e3695dba12b7de700bcfad0cca/uv-0.6.14-py3-none-win_amd64.whl", hash = "sha256:012f46bef6909209c4a6749e4019eb755ba762d37d7ceaaf76da9cb4b7f771e9", size = 17628508, upload-time = "2025-04-09T21:56:55.444Z" }, + { url = "https://files.pythonhosted.org/packages/77/09/163062d439ddc0d89e527ae0e631abf1f7781b183442d8823c48af368f5d/uv-0.6.14-py3-none-win_arm64.whl", hash = "sha256:7465081b4d0b213d0055ccb48de7fe546b5cf0853c6d3601115760760634f6d8", size = 16387232, upload-time = "2025-04-09T21:56:58.872Z" }, ] [[package]] name = "virtualenv" -version = "20.30.0" +version = "20.31.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945, upload_time = "2025-03-31T16:33:29.185Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461, upload_time = "2025-03-31T16:33:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, ] diff --git a/tests/linters/cspell/.cspell.json b/tests/linters/cspell/.cspell.json index 782430d..97ec9d4 100644 --- a/tests/linters/cspell/.cspell.json +++ b/tests/linters/cspell/.cspell.json @@ -28,6 +28,7 @@ "**/docs/doctree", "**/node_modules", "**/package-lock.json", + "**/unit_tests/coverage", "**/uv.lock" ], @@ -89,8 +90,11 @@ "Batchelder", "bcde", "bibfiles", + "booksubtitle", + "booktitle", "braycurtis", "buildinfo", + "bysource", "cblock", "CFF", "cheb", @@ -119,6 +123,7 @@ "dunder", "dvipng", "edcba", + "eigendecomposition", "eigenstuff", "eigsh", "eigval", @@ -127,13 +132,20 @@ "Envlist", "envname", "envsitepackagesdir", + "eprintclass", + "eprinttype", + "eventdate", + "eventtitle", + "eventtitleaddon", "exrc", "extlinks", "figsize", "fullspectral", + "Funcache", "genindex", "getfixturevalue", "getrev", + "Grégoire", "hashval", "hdbscan", "hellinger", @@ -142,16 +154,23 @@ "htmlvalidate", "ifmat", "imgmath", + "incollection", "inlinevar", + "intersphinx", "iobj", + "issn", + "issuetitle", "iterhash", "iterread", "iterwrite", "jacc", "jaccard", "jensenshannon", + "journaltitle", "keepdims", + "Klauschen", "kmeans", + "Kuczmarski", "kulczynski", "kulsinski", "Laplacians", @@ -162,7 +181,11 @@ "linestart", "linestop", "ller", + "LNCS", + "Luxburg", "mahal", + "maintitle", + "maintitleaddon", "mathjax", "membertype", "metacls", @@ -172,6 +195,7 @@ "modindex", "modname", "modpath", + "Montavon", "moveaxis", "msvs", "Müller", @@ -179,12 +203,14 @@ "ndarray", "ndarrays", "Neumann", + "nitpicky", "nosignatures", "notest", "nsamples", "nsri", "orcid", "overgeneral", + "pagetotal", "parseable", "Pattarawat", "pdist", @@ -195,12 +221,14 @@ "pnorm", "posargs", "prepreprocess", + "pubstate", "pybtex", "pydoclint", "pygtk", "pylintrc", "randn", "rcfile", + "reftypes", "regionref", "ritwickdey", "rodolphebarbanneau", @@ -209,6 +237,8 @@ "russelrao", "Samek", "seuclidean", + "shortjournal", + "shortseries", "showlocals", "skimage", "SNE", @@ -224,6 +254,7 @@ "subname", "subnames", "symmetrix", + "synset", "tamasfe", "testpaths", "texlive", @@ -234,10 +265,14 @@ "toxworkdir", "trange", "TSNE", + "typehints", "ufunc", + "Ulrike", "umap", + "urldate", "vimrc", "vsicons", + "Wäldchen", "wayou", "wminkowski", "Wojciech", diff --git a/tests/linters/cspell/package.json b/tests/linters/cspell/package.json index 456639a..d58d3f4 100644 --- a/tests/linters/cspell/package.json +++ b/tests/linters/cspell/package.json @@ -12,6 +12,6 @@ "cspell": "^8.16.1" }, "scripts": { - "cspell": "npx --prefix tests/linters/cspell cspell lint --config .cspell.json --root ../.. '**'" + "cspell": "npx cspell lint --config .cspell.json --root ../.. '**'" } } diff --git a/tests/unit_tests/corelay/__init__.py b/tests/unit_tests/corelay/__init__.py index 8b5ec7c..287b0ab 100644 --- a/tests/unit_tests/corelay/__init__.py +++ b/tests/unit_tests/corelay/__init__.py @@ -1 +1 @@ -"""A package that contains unit tests for the ``corelay`` package.""" +"""A package that contains unit tests for the :py:mod:`corelay` package.""" diff --git a/tests/unit_tests/corelay/io/__init__.py b/tests/unit_tests/corelay/io/__init__.py index 4d97bbc..8ada60e 100644 --- a/tests/unit_tests/corelay/io/__init__.py +++ b/tests/unit_tests/corelay/io/__init__.py @@ -1 +1 @@ -"""A sub-package that contains unit tests for the ``corelay.io`` sub-package.""" +"""A sub-package that contains unit tests for the :py:mod:`corelay.io` sub-package.""" diff --git a/tests/unit_tests/corelay/io/test_storage.py b/tests/unit_tests/corelay/io/test_storage.py index 48c8378..03959e6 100644 --- a/tests/unit_tests/corelay/io/test_storage.py +++ b/tests/unit_tests/corelay/io/test_storage.py @@ -1,23 +1,23 @@ -"""A module that contains unit tests for the ``corelay.io.storage`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.io.storage` module.""" +import typing from io import BytesIO from pathlib import Path import h5py import numpy import pytest -from numpy.typing import NDArray from corelay import io from corelay.io.storage import DataStorageBase, HashedHDF5 @pytest.fixture(name='data', scope='module') -def get_data_fixture() -> NDArray[numpy.float64]: +def get_data_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """A fixture that produces random test data with shape (10, 2). Returns: - NDArray[numpy.float64]: Returns random test data with shape (10, 2). + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns random test data with shape (10, 2). """ return numpy.random.rand(10, 2) @@ -25,21 +25,21 @@ def get_data_fixture() -> NDArray[numpy.float64]: @pytest.fixture(name='parameter_values', scope='module') def get_parameter_values_fixture() -> dict[str, int | str]: - """A fixture that produces a parameter values dictionary with param1=1 and param2='string'. + """A fixture that produces a parameter values :py:class:`dict` with `param1=1` and `param2='string'`. Returns: - dict[str, int | str]: Returns a dictionary with param1=1 and param2='string'. + dict[str, int | str]: Returns a :py:class:`dict` with `param1=1` and `param2='string'`. """ return {'param1': 1, 'param2': 'string'} class TestHashedHDF5: - """Contains unit tests for the ``HashedHDF5`` class.""" + """Contains unit tests for the :py:class:`~corelay.io.storage.HashedHDF5` class.""" @staticmethod def test_write_array() -> None: - """Tests that writing a NumPy array raises no exceptions""" + """Tests that writing a :py:class:`~numpy.ndarray` raises no exceptions""" with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: group = hdf5_file.require_group('hashed') @@ -49,7 +49,7 @@ def test_write_array() -> None: @staticmethod def test_write_tuple() -> None: - """Tests that writing a tuple of NumPy arrays raises no exceptions""" + """Tests that writing a tuple of :py:class:`~numpy.ndarray` raises no exceptions""" with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: group = hdf5_file.require_group('hashed') @@ -59,7 +59,7 @@ def test_write_tuple() -> None: @staticmethod def test_write_unsupported() -> None: - """Tests that writing an unsupported type raises a ``TypeError``""" + """Tests that writing an unsupported type raises a :py:class:`TypeError`""" with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: group = hdf5_file.require_group('hashed') @@ -95,7 +95,9 @@ def test_read_tuple() -> None: @pytest.mark.parametrize('data_storage_type', [io.HDF5Storage, io.PickleStorage]) def test_data_storage_at_functionality(data_storage_type: type[io.HDF5Storage | io.PickleStorage], tmp_path: Path) -> None: - """Tests the reading and writing of data from ``HDF5storage`` and ``PickleStorage`` data storage containers using the `at(data_key)` method. + """Tests the reading and writing of data from :py:class:`~corelay.io.storage.HDF5Storage` and :py:class:`~corelay.io.storage.PickleStorage` data + storage containers using the :py:meth:`HDF5Storage.at ` or + :py:meth:`PickleStorage.at ` methods. Args: data_storage_type (type[io.HDF5Storage | io.PickleStorage]): The storage class to be tested. @@ -125,16 +127,17 @@ def test_data_storage_at_functionality(data_storage_type: type[io.HDF5Storage | def test_data_storage( data_storage_type: type[io.HDF5Storage | io.PickleStorage], tmp_path: Path, - data: NDArray[numpy.float64], + data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], parameter_values: dict[str, int | str] ) -> None: - """Tests the reading and writing of data to and from ``HDF5storage`` and ``PickleStorage`` data storage containers. + """Tests the reading and writing of data to and from :py:class:`~corelay.io.storage.HDF5Storage` and :py:class:`~corelay.io.storage.PickleStorage` + data storage containers. Args: data_storage_type (type[io.HDF5Storage | io.PickleStorage]): The storage class to be tested. tmp_path (Path): A temporary path for testing. - data (NDArray[numpy.float64]): Random test data with shape (10, 2). - parameter_values (dict[str, int | str]): A dictionary with param1=1 and param2='string'. + data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): Random test data with shape (10, 2). + parameter_values (dict[str, int | str]): A :py:class:`dict` with `param1=1` and `param2='string'`. """ # Tests writing data @@ -175,11 +178,12 @@ def test_data_storage( numpy.testing.assert_equal(returned_data, data) -def test_no_storage(data: NDArray[numpy.float64]) -> None: - """Tests the reading and writing of data to and from a ``NoStorage`` data storage container that raises exceptions when reading and writing. +def test_no_storage(data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: + """Tests the reading and writing of data to and from a :py:class:`~corelay.io.NoStorage` data storage container that raises exceptions when + reading and writing. Args: - data (NDArray[numpy.float64]): Random test data with shape (10, 2). + data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): Random test data with shape (10, 2). """ data_storage = io.NoStorage() diff --git a/tests/unit_tests/corelay/pipeline/__init__.py b/tests/unit_tests/corelay/pipeline/__init__.py index b35cd23..5b0478e 100644 --- a/tests/unit_tests/corelay/pipeline/__init__.py +++ b/tests/unit_tests/corelay/pipeline/__init__.py @@ -1 +1 @@ -"""A sub-package that contains unit tests for the ``corelay.pipeline`` sub-package.""" +"""A sub-package that contains unit tests for the :py:mod:`corelay.pipeline` sub-package.""" diff --git a/tests/unit_tests/corelay/pipeline/test_base.py b/tests/unit_tests/corelay/pipeline/test_base.py index 8346f8b..26779a5 100644 --- a/tests/unit_tests/corelay/pipeline/test_base.py +++ b/tests/unit_tests/corelay/pipeline/test_base.py @@ -1,8 +1,9 @@ -"""A module that contains unit tests for the ``corelay.io.base`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.io.base` module.""" -from collections import OrderedDict +import collections +import typing from types import FunctionType -from typing import Annotated, Any +from typing import Annotated import pytest @@ -13,26 +14,26 @@ @pytest.fixture(name='processor_type', scope='module') def get_processor_type_fixture() -> type[Processor]: - """A fixture that produces a custom ``Processor`` type. + """A fixture that produces a custom :py:class:`~corelay.processor.base.Processor` type. Returns: - type[Processor]: Returns a custom ``Processor`` type. + type[Processor]: Returns a custom :py:class:`~corelay.processor.base.Processor` type. """ class MyProcessor(Processor): - """A custom ``Processor`` type.""" + """A custom :py:class:`~corelay.processor.base.Processor` type.""" - param_1: Annotated[Any, Param(Any)] - param_2: Annotated[Any, Param(Any)] + param_1: Annotated[typing.Any, Param(typing.Any)] + param_2: Annotated[typing.Any, Param(typing.Any)] - def function(self, data: Any) -> Any: + def function(self, data: typing.Any) -> typing.Any: """Multiplies the input data by 2. Args: - data (Any): The input data that is to be processed. + data (typing.Any): The input data that is to be processed. Returns: - Any: Returns the processed data. + typing.Any: Returns the processed data. """ return data * 2 @@ -42,80 +43,88 @@ def function(self, data: Any) -> Any: @pytest.fixture(name='pipeline_type', scope='module') def get_pipeline_type_fixture(processor_type: type[Processor]) -> type[Pipeline]: - """A fixture that produces a custom ``Pipeline`` type. + """A fixture that produces a custom :py:class:`~corelay.pipeline.base.Pipeline` type. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the custom ``Pipeline`` type. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the custom + :py:class:`~corelay.pipeline.base.Pipeline` type. Returns: - type[Pipeline]: Returns a custom ``Pipeline`` type. + type[Pipeline]: Returns a custom :py:class:`~corelay.pipeline.base.Pipeline` type. """ class MyPipeline(Pipeline): - """A custom ``Pipeline`` type.""" + """A custom :py:class:`~corelay.pipeline.base.Pipeline` type.""" - task_1 = Task(FunctionProcessor, lambda self, x: x + 3, is_output=False, bind_method=True) - task_2 = Task(processor_type, processor_type(), is_output=True) + task_1: Annotated[FunctionProcessor, Task(FunctionProcessor, lambda self, x: x + 3, is_output=False, bind_method=True)] + task_2: Annotated[FunctionProcessor, Task(processor_type, processor_type(), is_output=True)] return MyPipeline @pytest.fixture(name='pipeline_with_multiple_outputs_type', scope='module') def get_pipeline_with_multiple_outputs_type_fixture() -> type[Pipeline]: - """A fixture that produces a custom ``Pipeline`` type with multiple outputs. + """A fixture that produces a custom :py:class:`~corelay.pipeline.base.Pipeline` type with multiple outputs. Returns: - type[Pipeline]: Returns a custom ``Pipeline`` type with multiple outputs. + type[Pipeline]: Returns a custom :py:class:`~corelay.pipeline.base.Pipeline` type with multiple outputs. """ class MyPipeline(Pipeline): - """A custom ``Pipeline`` type with multiple outputs.""" + """A custom :py:class:`~corelay.pipeline.base.Pipeline` type with multiple outputs.""" - task_1 = Task(FunctionProcessor, lambda self, x: x + 2, is_output=True, bind_method=True) - task_2 = Task(FunctionProcessor, lambda self, x: x * 2, is_output=True, bind_method=True) + task_1: Annotated[FunctionProcessor, Task(FunctionProcessor, lambda self, x: x + 2, is_output=True, bind_method=True)] + task_2: Annotated[FunctionProcessor, Task(FunctionProcessor, lambda self, x: x * 2, is_output=True, bind_method=True)] return MyPipeline class TestTask: - """Contains unit tests for the ``Task`` class.""" + """Contains unit tests for the :py:class:`~corelay.pipeline.base.Task` class.""" @staticmethod def test_instantiation_default() -> None: - """Tests that the instantiation of a ``Task`` without any arguments succeeds.""" + """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` without any arguments succeeds.""" Task() @staticmethod def test_instantiation_arguments(processor_type: type[Processor]) -> None: - """Tests that the instantiation of a ``Task`` with correct arguments succeeds. + """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with correct arguments succeeds. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the ``Task``. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the + :py:class:`~corelay.pipeline.base.Task`. """ Task(proc_type=processor_type, default=processor_type(), is_output=True) @staticmethod def test_proc_type_no_proc() -> None: - """Tests that the instantiation of a ``Task`` with a ``proc_type`` that is not a sub-class of ``Processor`` raises a ''TypeError''.""" + """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with a ``proc_type`` that is not a sub-class of + :py:class:`~corelay.processor.base.Processor` raises a :py:class:`TypeError`. + """ with pytest.raises(TypeError): Task(proc_type=FunctionType, default=lambda x: x) # type: ignore[arg-type] @staticmethod def test_default_no_proc() -> None: - """Tests that the instantiation of a ``Task`` with a default value that is not of type ``Processor`` fails.""" + """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with a default value that is not of type + :py:class:`~corelay.processor.base.Processor` fails. + """ with pytest.raises(TypeError): Task(default='bla') # type: ignore[arg-type] @staticmethod def test_proc_type_default_type_mismatch(processor_type: type[Processor]) -> None: - """Tests that the instantiation of a ``Task`` with a default value that is not of type ``proc_type`` raises a ``TypeError``. + """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with a default value that is not of type ``proc_type`` raises a + :py:class:`TypeError`. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the ``Task``. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the + :py:class:`~corelay.pipeline.base.Task`. """ with pytest.raises(TypeError): @@ -123,7 +132,7 @@ def test_proc_type_default_type_mismatch(processor_type: type[Processor]) -> Non @staticmethod def test_default_function_identity() -> None: - """Tests that the default function of a ``FunctionProcessor`` is the identity function.""" + """Tests that the default function of a :py:class:`~corelay.processor.base.FunctionProcessor` is the identity function.""" # For some reason, PyLint does not recognize that the type of the default value is Processor and/or that Processor is callable task = Task() @@ -132,10 +141,11 @@ def test_default_function_identity() -> None: @staticmethod def test_assigned_default(processor_type: type[Processor]) -> None: - """Tests that assigning a default ``Processor`` value succeeds. + """Tests that assigning a default :py:class:`~corelay.processor.base.Processor` value succeeds. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the ``Task``. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the + :py:class:`~corelay.pipeline.base.Task`. """ # For some reason, PyLint does not recognize that the type of the default value is Processor and/or that Processor is callable @@ -145,31 +155,31 @@ def test_assigned_default(processor_type: type[Processor]) -> None: class TestPipeline: - """Contains unit tests for the ``Pipeline`` class.""" + """Contains unit tests for the :py:class:`~corelay.pipeline.base.Pipeline` class.""" @staticmethod def test_instantiation_base() -> None: - """Tests that the instantiation of the base class ``Pipeline`` without any arguments succeeds.""" + """Tests that the instantiation of the base class :py:class:`~corelay.pipeline.base.Pipeline` without any arguments succeeds.""" Pipeline() @staticmethod def test_instantiation_default(pipeline_type: type[Pipeline]) -> None: - """Tests that the instantiation of a custom sub-class of ``Pipeline`` without any arguments succeeds. + """Tests that the instantiation of a custom sub-class of :py:class:`~corelay.pipeline.base.Pipeline` without any arguments succeeds. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. """ pipeline_type() @staticmethod def test_instantiation_arguments(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: - """Tests that the instantiation of a custom sub-class of ``Pipeline`` with the correct arguments succeeds. + """Tests that the instantiation of a custom sub-class of :py:class:`~corelay.pipeline.base.Pipeline` with the correct arguments succeeds. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ pipeline_type(task_1=lambda x: x + 2, task_2=processor_type()) @@ -179,7 +189,7 @@ def test_default_call(pipeline_type: type[Pipeline]) -> None: """Tests that running a pipeline with all defaults in place succeeds. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. """ pipeline = pipeline_type() @@ -187,10 +197,10 @@ def test_default_call(pipeline_type: type[Pipeline]) -> None: @staticmethod def test_default_call_no_input(pipeline_type: type[Pipeline]) -> None: - """Tests that running a pipeline without an input raises a ``TypeError``. + """Tests that running a pipeline without an input raises a :py:class:`TypeError`. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. """ pipeline = pipeline_type() @@ -202,7 +212,7 @@ def test_default_call_output(pipeline_type: type[Pipeline]) -> None: """Tests that running a pipeline with the default processors returns the correct output. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. """ pipeline = pipeline_type() @@ -215,7 +225,8 @@ def test_default_call_output_multiple(pipeline_with_multiple_outputs_type: type[ """Tests that processors that have multiple outputs, correctly output a tuple containing the outputs. Args: - pipeline_with_multiple_outputs_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. + pipeline_with_multiple_outputs_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in + the test. """ pipeline = pipeline_with_multiple_outputs_type() @@ -228,8 +239,8 @@ def test_default_param_values(pipeline_type: type[Pipeline], processor_type: typ """Tests that the default parameter values are assigned correctly. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(is_output=False) @@ -242,23 +253,23 @@ def test_checkpoint_processes(pipeline_type: type[Pipeline], processor_type: typ """Tests that collecting all processors relevant to a checkpoint succeeds. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ first_processor = FunctionProcessor(processing_function=lambda x: x + 5, is_checkpoint=False) second_processor = processor_type(is_checkpoint=True) pipeline = pipeline_type(task_1=first_processor, task_2=second_processor) - assert pipeline.checkpoint_processes() == OrderedDict(task_2=second_processor) + assert pipeline.checkpoint_processes() == collections.OrderedDict(task_2=second_processor) @staticmethod def test_checkpoint_data(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: """Tests that the checkpoint data is stored correctly. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ first_processor = FunctionProcessor(processing_function=lambda x: x + 5, is_checkpoint=True) @@ -273,8 +284,8 @@ def test_from_checkpoint(pipeline_type: type[Pipeline], processor_type: type[Pro """Tests that resuming from a checkpoint succeeds. Args: - pipeline_type (type[Pipeline]): The custom ``Pipeline`` type that is to be used in the test. - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ first_processor = FunctionProcessor(processing_function=lambda x: x + 5, is_checkpoint=True) diff --git a/tests/unit_tests/corelay/pipeline/test_spectral.py b/tests/unit_tests/corelay/pipeline/test_spectral.py index d5def88..e1b1a06 100644 --- a/tests/unit_tests/corelay/pipeline/test_spectral.py +++ b/tests/unit_tests/corelay/pipeline/test_spectral.py @@ -1,11 +1,10 @@ -"""A module that contains unit tests for the ``corelay.io.spectral`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.io.spectral` module.""" import os +import typing import numpy import pytest -from numpy.typing import NDArray - from matplotlib import pyplot from corelay.pipeline.spectral import SpectralEmbedding, SpectralClustering @@ -16,14 +15,14 @@ @pytest.fixture(name='spiral_data', scope='module') -def get_spiral_data_fixture(number_of_samples_per_class: int = 150) -> NDArray[numpy.float64]: +def get_spiral_data_fixture(number_of_samples_per_class: int = 150) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """A fixture that produces data points that has the shape of a spiral. Args: - number_of_samples_per_class (int, optional): Number of samples per class. Defaults to 150. + number_of_samples_per_class (int): Number of samples per class. Defaults to 150. Returns: - NDArray[numpy.float64]: Returns a NumPy array of shape (300, 2) containing the spiral data. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns a NumPy array of shape (300, 2) containing the spiral data. """ # Fixes the seed for the random number generator to ensure reproducibility (100 random numbers are sampled uniformly as "part of the seed"; I do @@ -46,10 +45,10 @@ def get_spiral_data_fixture(number_of_samples_per_class: int = 150) -> NDArray[n @pytest.fixture(name='number_of_neighbors', scope='module') -def get_number_of_neighbors_fixture(spiral_data: NDArray[numpy.float64]) -> int: +def get_number_of_neighbors_fixture(spiral_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> int: """A fixture that choose a suitable number of neighbors for the k-nearest neighbors algorithm. Args: - spiral_data (NDArray[numpy.float64]): The spiral test data. + spiral_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The spiral test data. Returns: int: Returns a suitable number of neighbors for the k-nearest neighbors algorithm. @@ -81,7 +80,9 @@ def get_number_of_clusters_fixture() -> int: class TestSpectralEmbeddingAndSpectralClustering: - """Contains unit tests for the ``SpectralEmbedding`` and ``SpectralClustering`` classes.""" + """Contains unit tests for the :py:class:`~corelay.pipeline.spectral.SpectralEmbedding` and + :py:class:`~corelay.pipeline.spectral.SpectralClustering` classes. + """ @staticmethod def test_spectral_embedding_instantiation() -> None: @@ -96,22 +97,23 @@ def test_spectral_clustering_instantiation() -> None: SpectralClustering() @staticmethod - def test_data_generation(spiral_data: NDArray[numpy.float64]) -> None: + def test_data_generation(spiral_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: """Performs a sanity check to make sure the data looks as expected (from the outside). Args: - spiral_data (NDArray[numpy.float64]): The spiral test data. + spiral_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The spiral test data. """ assert isinstance(spiral_data, numpy.ndarray), f'Expected NumPy arrays, got {type(spiral_data)}.' assert spiral_data.shape == (300, 2), f'Expected the spiral test data to be of shape (300, 2), got {spiral_data.shape}.' @staticmethod - def test_spectral_embedding_default_params(spiral_data: NDArray[numpy.float64]) -> None: - """Tests whether the ``SpectralEmbedding`` operates on data all the way through, using its default parameters. + def test_spectral_embedding_default_params(spiral_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: + """Tests whether the :py:class:`~corelay.pipeline.spectral.SpectralEmbedding` operates on data all the way through, using its default + parameters. Args: - spiral_data (NDArray[numpy.float64]): The spiral test data. + spiral_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The spiral test data. """ pipeline = SpectralEmbedding() @@ -131,11 +133,12 @@ def test_spectral_embedding_default_params(spiral_data: NDArray[numpy.float64]) ) @staticmethod - def test_spectral_clustering_default_params(spiral_data: NDArray[numpy.float64]) -> None: - """Tests whether the ``SpectralClustering`` operates on data all the way through, using its default parameters. + def test_spectral_clustering_default_params(spiral_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: + """Tests whether the :py:class:`~corelay.pipeline.spectral.SpectralClustering` operates on data all the way through, using its default + parameters. Args: - spiral_data (NDArray[numpy.float64]): The spiral test data. + spiral_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The spiral test data. """ pipeline = SpectralClustering() @@ -168,7 +171,7 @@ def test_spectral_clustering_default_params(spiral_data: NDArray[numpy.float64]) @staticmethod def test_spectral_clustering_step_by_step_custom_params( - spiral_data: NDArray[numpy.float64], + spiral_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], number_of_neighbors: int, number_of_eigenvalues: int, number_of_clusters: int, @@ -179,7 +182,7 @@ def test_spectral_clustering_step_by_step_custom_params( pipeline output. Args: - spiral_data (NDArray[numpy.float64]): The spiral test data. + spiral_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The spiral test data. number_of_neighbors (int): The number of neighbors for the k-nearest neighbors algorithm. number_of_eigenvalues (int): The number of eigenvalues to compute. number_of_clusters (int): The number of clusters for the k-means algorithm. diff --git a/tests/unit_tests/corelay/processor/__init__.py b/tests/unit_tests/corelay/processor/__init__.py index bf70dea..d02371e 100644 --- a/tests/unit_tests/corelay/processor/__init__.py +++ b/tests/unit_tests/corelay/processor/__init__.py @@ -1 +1 @@ -"""A sub-package that contains unit tests for the ``corelay.processor`` sub-package.""" +"""A sub-package that contains unit tests for the :py:mod:`corelay.processor` sub-package.""" diff --git a/tests/unit_tests/corelay/processor/test_base.py b/tests/unit_tests/corelay/processor/test_base.py index d09c44c..d340bbb 100644 --- a/tests/unit_tests/corelay/processor/test_base.py +++ b/tests/unit_tests/corelay/processor/test_base.py @@ -1,7 +1,8 @@ -"""A module that contains unit tests for the ``corelay.processor.base`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.processor.base` module.""" +import typing from collections.abc import Callable -from typing import Annotated, Any +from typing import Annotated import pytest @@ -12,14 +13,14 @@ @pytest.fixture(name='processor_type', scope='module') def get_processor_type_fixture() -> type[Processor]: - """A fixture that produces a custom ``Processor`` type. + """A fixture that produces a custom :py:class:`~corelay.processor.base.Processor` type. Returns: - type[Processor]: Returns a custom ``Processor`` type. + type[Processor]: Returns a custom :py:class:`~corelay.processor.base.Processor` type. """ class MyProcessor(Processor): - """A custom ``Processor`` type.""" + """A custom :py:class:`~corelay.processor.base.Processor` type.""" param_1: Annotated[str, Param(str, mandatory=True)] param_2: Annotated[int, Param(int, -25, positional=True)] @@ -27,14 +28,14 @@ class MyProcessor(Processor): value = 42 text = 'apple' - def function(self, data: Any) -> Any: # pylint: disable=unused-argument - """An example function that implements the logic of the custom ``Processor``. + def function(self, data: typing.Any) -> typing.Any: # pylint: disable=unused-argument + """An example function that implements the logic of the custom :py:class:`~corelay.processor.base.Processor`. Args: - data (Any): The input data, which is not used. + data (typing.Any): The input data, which is not used. Returns: - Any: Returns a fixed value of 21. + typing.Any: Returns a fixed value of 21. """ return 21 @@ -43,11 +44,13 @@ def function(self, data: Any) -> Any: # pylint: disable=unused-argument @pytest.fixture(name='kwargs', scope='module') -def get_kwargs_fixture() -> dict[str, Any]: - """A fixture that produces a dictionary with valid ``Param`` values for a ``Processor``. +def get_kwargs_fixture() -> dict[str, typing.Any]: + """A fixture that produces a :py:class:`dict` with valid :py:class:`~corelay.base.Param` values for a + :py:class:`~corelay.processor.base.Processor`. Returns: - dict[str, Any]: Returns a dictionary with valid ``Param`` values for a ``Processor``. + dict[str, typing.Any]: Returns a :py:class:`dict` with valid :py:class:`~corelay.base.Param` values for a + :py:class:`~corelay.processor.base.Processor`. """ return { @@ -108,14 +111,14 @@ def some_function(_data: int) -> int: class TestProcessor: - """Contains unit tests for the ``Processor`` class.""" + """Contains unit tests for the :py:class:`~corelay.processor.base.Processor` class.""" @staticmethod def test_params_tracked(processor_type: type[Processor]) -> None: """Tests that processors track parameters correctly. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ assert ( @@ -127,18 +130,19 @@ def test_creation(processor_type: type[Processor]) -> None: """Tests that processors instantiate properly in all cases. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor_type() @staticmethod - def test_instance_assign(processor_type: type[Processor], kwargs: dict[str, Any]) -> None: + def test_instance_assign(processor_type: type[Processor], kwargs: dict[str, typing.Any]) -> None: """Tests that the parameter values passed as keyword arguments during instantiation are properly set. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. - kwargs (dict[str, Any]): A dictionary with valid ``Param`` values for a ``Processor``. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. + kwargs (dict[str, typing.Any]): A :py:class:`dict` with valid :py:class:`~corelay.base.Param` values for a + :py:class:`~corelay.processor.base.Processor`. """ processor = processor_type(**kwargs) @@ -149,7 +153,7 @@ def test_instance_default(processor_type: type[Processor]) -> None: """Tests that the default values are be properly. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='bacon') @@ -162,7 +166,7 @@ def test_instance_positional(processor_type: type[Processor]) -> None: """Tests that the positional values are properly assigned. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(44) @@ -175,7 +179,7 @@ def test_unknown_param(processor_type: type[Processor]) -> None: """Tests that unknown parameters raise an exception. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ with pytest.raises(TypeError): @@ -183,7 +187,7 @@ def test_unknown_param(processor_type: type[Processor]) -> None: @staticmethod def test_abstract_func() -> None: - """Tests that the ``Processor`` class is abstract and thus fails to instantiate.""" + """Tests that the :py:class:`~corelay.processor.base.Processor` class is abstract and thus fails to instantiate.""" with pytest.raises(TypeError): Processor() # type: ignore[abstract] # pylint: disable=abstract-class-instantiated @@ -193,7 +197,7 @@ def test_checkpoint(processor_type: type[Processor]) -> None: """Tests that checkpoints properly store data. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='bacon', is_checkpoint=True) @@ -202,23 +206,25 @@ def test_checkpoint(processor_type: type[Processor]) -> None: assert processor.checkpoint_data == output @staticmethod - def test_param_values(processor_type: type[Processor], kwargs: dict[str, Any]) -> None: + def test_param_values(processor_type: type[Processor], kwargs: dict[str, typing.Any]) -> None: """Tests that the parameters are correctly set in the constructor. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. - kwargs (dict[str, Any]): A dictionary with valid ``Param`` values for a ``Processor``. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. + kwargs (dict[str, typing.Any]): A :py:class:`dict` with valid :py:class:`~corelay.base.Param` values for a + :py:class:`~corelay.processor.base.Processor`. """ processor = processor_type(**kwargs) assert processor.param_values() == kwargs @staticmethod - def test_copy_param_values(processor_type: type[Processor], kwargs: dict[str, Any]) -> None: + def test_copy_param_values(processor_type: type[Processor], kwargs: dict[str, typing.Any]) -> None: """Tests that copies of processors have identical Param values. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. - kwargs (dict[str, Any]): A dictionary with valid ``Param`` values for a ``Processor``. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. + kwargs (dict[str, typing.Any]): A :py:class:`dict` with valid :py:class:`~corelay.base.Param` values for a + :py:class:`~corelay.processor.base.Processor`. """ processor = processor_type(**kwargs) @@ -228,10 +234,10 @@ def test_copy_param_values(processor_type: type[Processor], kwargs: dict[str, An @staticmethod def test_multiple_dtype(processor_type: type[Processor]) -> None: - """Tests that a ``Parameter`` can be of multiple types. + """Tests that a :py:class:`~corelay.base.Param` can be of multiple types. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor_type(param_1='soup', param_3=21) @@ -242,7 +248,7 @@ def test_mandatory_param(processor_type: type[Processor]) -> None: """Tests that mandatory parameters raise an exception when accessed without being set. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type() @@ -252,10 +258,10 @@ def test_mandatory_param(processor_type: type[Processor]) -> None: @staticmethod def test_wrong_type_param(processor_type: type[Processor]) -> None: - """Tests that passing a value with wrong the type raises a ``TypeError``. + """Tests that passing a value with wrong the type raises a :py:class:`TypeError`. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ with pytest.raises(TypeError): @@ -263,23 +269,25 @@ def test_wrong_type_param(processor_type: type[Processor]) -> None: @staticmethod def test_bad_dtype() -> None: - """Tests that the the ``dtype`` only accepts types, i.e., non-type values cannot be passed as argument for the ``dtype`` parameter.""" + """Tests that the the :py:attr:`~corelay.plugboard.Slot.dtype` only accepts types, i.e., non-type values cannot be passed as argument for the + ``dtype`` parameter. + """ with pytest.raises(TypeError): class TestProcessorWithWrongParam(Processor): - """A custom ``Processor`` with wrong a Param.""" + """A custom :py:class:`~corelay.processor.base.Processor` with wrong a Param.""" param: Annotated[int, Param(2)] - """A wrong ``Param`` with a non-type value as dtype.""" + """A wrong :py:class:`~corelay.base.Param` with a non-type value as dtype.""" - def function(self, data: Any) -> Any: - """An example function that implements the logic of the custom ``Processor``. + def function(self, data: typing.Any) -> typing.Any: + """An example function that implements the logic of the custom :py:class:`~corelay.processor.base.Processor`. Args: - data (Any): The input data. + data (typing.Any): The input data. Returns: - Any: Returns the input data. + typing.Any: Returns the input data. """ return data @@ -288,10 +296,10 @@ def function(self, data: Any) -> Any: @staticmethod def test_update_defaults(processor_type: type[Processor]) -> None: - """Tests that the default values of a ``Parameter`` instance can be updated. + """Tests that the default values of a :py:class:`~corelay.base.Param` instance can be updated. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='soup') @@ -303,10 +311,10 @@ def test_update_defaults(processor_type: type[Processor]) -> None: @staticmethod def test_reset_defaults(processor_type: type[Processor]) -> None: - """Tests that the default values of a ``Parameter`` instance can be reset. + """Tests that the default values of a :py:class:`~corelay.base.Param` instance can be reset. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='soup') @@ -318,10 +326,11 @@ def test_reset_defaults(processor_type: type[Processor]) -> None: @staticmethod def test_reset_defaults_assigned(processor_type: type[Processor]) -> None: - """Tests that resetting the default values of ``Param`` instances go back to returning instantiation-time default values. + """Tests that resetting the default values of :py:class:`~corelay.base.Param` instances go back to returning instantiation-time default + values. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='soup', param_2=2) @@ -333,10 +342,10 @@ def test_reset_defaults_assigned(processor_type: type[Processor]) -> None: @staticmethod def test_update_defaults_wrong_dtype(processor_type: type[Processor]) -> None: - """Tests that updating the default values of ``Param`` instances with the wrong type raises a ``TypeError``. + """Tests that updating the default values of :py:class:`~corelay.base.Param` instances with the wrong type raises a :py:class:`TypeError`. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='soup') @@ -346,11 +355,12 @@ def test_update_defaults_wrong_dtype(processor_type: type[Processor]) -> None: class TestFunctionProcessor: - """Contains unit tests for the ``FunctionProcessor`` class.""" + """Contains unit tests for the :py:class:`~corelay.processor.base.FunctionProcessor` class.""" @staticmethod def test_instantiation(unbound_function: Callable[[int], int]) -> None: - """Tests that the instantiation of a ``FunctionProcessor`` with an unbound function as a keyword argument should work. + """Tests that the instantiation of a :py:class:`~corelay.processor.base.FunctionProcessor` with an unbound function as a keyword argument + should work. Args: unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. @@ -360,8 +370,8 @@ def test_instantiation(unbound_function: Callable[[int], int]) -> None: @staticmethod def test_instance_call(unbound_function: Callable[[int], int]) -> None: - """Tests that calling a ``FunctionProcessor`` instance, that was constructed with a function that was passed as a keyword argument, results in - the same output as calling the function directly. + """Tests that calling a :py:class:`~corelay.processor.base.FunctionProcessor` instance, that was constructed with a function that was passed + as a keyword argument, results in the same output as calling the function directly. Args: unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. @@ -372,8 +382,8 @@ def test_instance_call(unbound_function: Callable[[int], int]) -> None: @staticmethod def test_instance_call_positional(unbound_function: Callable[[int], int]) -> None: - """Tests that calling a ``FunctionProcessor`` instance, that was constructed with a function that was passed as a positional argument, results - in the same output as calling the function directly. + """Tests that calling a :py:class:`~corelay.processor.base.FunctionProcessor` instance, that was constructed with a function that was passed + as a positional argument, results in the same output as calling the function directly. Args: unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. @@ -395,8 +405,8 @@ def test_instance_call_bound(function: Callable[[Processor, int], int]) -> None: @staticmethod def test_non_callable() -> None: - """Tests that passing a non-callable object as an argument for the ``function`` parameter of the ``FunctionProcessor`` constructor raises a - ``TypeError``. + """Tests that passing a non-callable object as an argument for the ``processing_function`` parameter of the + :py:class:`~corelay.processor.base.FunctionProcessor` constructor raises a :py:class:`TypeError`. """ with pytest.raises(TypeError): @@ -404,15 +414,15 @@ def test_non_callable() -> None: class TestEnsureProcessor: - """Contains unit tests for the ``ensure_processor`` function.""" + """Contains unit tests for the :py:func:`~corelay.processor.base.ensure_processor` function.""" @staticmethod def test_processor(processor_type: type[Processor]) -> None: - """Tests that passing an existing ``Processor`` to the ``ensure_processor`` function returns the original ``Processor`` instead of creating a - new one. + """Tests that passing an existing :py:class:`~corelay.processor.base.Processor` to the :py:func:`~corelay.processor.base.ensure_processor` + function returns the original :py:class:`~corelay.processor.base.Processor` instead of creating a new one. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='giraffe') @@ -422,7 +432,9 @@ def test_processor(processor_type: type[Processor]) -> None: @staticmethod def test_function(unbound_function: Callable[[int], int]) -> None: - """Tests that passing a function to the ``ensure_processor`` function returns a new ``FunctionProcessor`` instance that wraps the function. + """Tests that passing a function to the :py:func:`~corelay.processor.base.ensure_processor` function returns a new + :py:class:`~corelay.processor.base.FunctionProcessor` instance that wraps the function. + Args: unbound_function (Callable[[int], int]): The unbound function that is to be used in the test. """ @@ -432,18 +444,20 @@ def test_function(unbound_function: Callable[[int], int]) -> None: @staticmethod def test_invalid() -> None: - """Tests that passing a non-callable object that is not of type ``Processor`` to the ``ensure_processor`` function raises a ''TypeError''.""" + """Tests that passing a non-callable object that is not of type :py:class:`~corelay.processor.base.Processor` to the + :py:func:`~corelay.processor.base.ensure_processor` function raises a :py:class:`TypeError`. + """ with pytest.raises(TypeError): ensure_processor('mummy') # type: ignore[arg-type] @staticmethod def test_default_param_omitted(processor_type: type[Processor]) -> None: - """Tests that passing a ``Param`` value as a keyword argument to the ``ensure_processor`` function sets the value of the ``Param`` in the - returned ``Processor`` instance. + """Tests that passing a :py:class:`~corelay.base.Param` value as a keyword argument to the :py:func:`~corelay.processor.base.ensure_processor` + function sets the value of the :py:class:`~corelay.base.Param` in the returned :py:class:`~corelay.processor.base.Processor` instance. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='giraffe') @@ -453,11 +467,12 @@ def test_default_param_omitted(processor_type: type[Processor]) -> None: @staticmethod def test_default_param_assigned(processor_type: type[Processor]) -> None: - """Tests that passing a ``Param`` value as a keyword argument the the ``ensure_processor`` function has a lower priority than explicitly set - ``Param`` values and should not overwrite them. + """Tests that passing a :py:class:`~corelay.base.Param` value as a keyword argument the the + :py:func:`~corelay.processor.base.ensure_processor` function has a lower priority than explicitly set :py:class:`~corelay.base.Param` values + and should not overwrite them. Args: - processor_type (type[Processor]): The custom ``Processor`` type that is to be used in the test. + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. """ processor = processor_type(param_1='giraffe', is_output=False) diff --git a/tests/unit_tests/corelay/processor/test_clustering.py b/tests/unit_tests/corelay/processor/test_clustering.py index fd76dcb..ac651f7 100644 --- a/tests/unit_tests/corelay/processor/test_clustering.py +++ b/tests/unit_tests/corelay/processor/test_clustering.py @@ -1,12 +1,11 @@ -"""A module that contains unit tests for the ``corelay.processor.clustering`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.processor.clustering` module.""" import os +import typing from importlib import import_module -from typing import Any import numpy import pytest -from numpy.typing import NDArray from sklearn.datasets import make_blobs from sklearn.metrics.pairwise import euclidean_distances @@ -27,54 +26,57 @@ @pytest.fixture(name='data', scope='module') -def get_data_fixture() -> NDArray[numpy.float64]: +def get_data_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """A fixture that produces tests data with 1000 elements that are split into 5 blobs. Returns: - NDArray[numpy.float64]: Returns the data, which is a 2D array of shape (1000, 2), i.e., 1000 samples with 2 features. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns the data, which is a 2D array of shape `(1000, 2)`, i.e., 1000 samples with 2 + features. """ blobs = make_blobs(1000, centers=5, random_state=100) - blob: NDArray[numpy.float64] = blobs[0] + blob: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = blobs[0] return blob @pytest.fixture(name='tiny_data', scope='module') -def get_tiny_data_fixture() -> NDArray[numpy.float64]: +def get_tiny_data_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """A fixtures that produces tiny test data with 50 elements that are split into 5 blobs. Returns: - NDArray[numpy.float64]: Returns the data, which is a 2D array of shape (50, 2), i.e., 50 samples with 2 features. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns the data, which is a 2D array of shape `(50, 2)`, i.e., 50 samples with 2 + features. """ blobs = make_blobs(50, centers=5, random_state=100) - blob: NDArray[numpy.float64] = blobs[0] + blob: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = blobs[0] return blob @pytest.fixture(name='distances', scope='module') -def get_distances_fixture(data: NDArray[numpy.float64]) -> NDArray[numpy.float64]: +def get_distances_fixture(data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """A fixtures that computes the euclidean distances between each pair of data points. Args: - data (NDArray[numpy.float64]): The data to compute the distances for. + data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The data to compute the distances for. Returns: - NDArray[numpy.float64]: Returns a distance matrix, which, given input data of shape (, ), is a 2D array - of shape (, ), i.e., the distances between each pair of samples. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns a distance matrix, which, given input data of shape + `(, )`, is a 2D array of shape `(, )`, i.e., the distances + between each pair of samples. """ - distance_matrix: NDArray[numpy.float64] = euclidean_distances(data) + distance_matrix: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = euclidean_distances(data) return distance_matrix @pytest.mark.parametrize('processor_type', [clustering.AgglomerativeClustering, clustering.KMeans]) -def test_clustering(processor_type: type[clustering.Clustering], data: NDArray[numpy.float64]) -> None: +def test_clustering(processor_type: type[clustering.Clustering], data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: """Tests the clustering processors by checking that the 5 found clusters are approximately of similar size. Args: - processor_type (type[clustering.Clustering]): The clustering ``Processor`` that is to be used in the test. - data (NDArray[numpy.float64]): The data that is to be clustered. + processor_type (type[clustering.Clustering]): The clustering :py:class:`~corelay.processor.base.Processor` that is to be used in the test. + data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The data that is to be clustered. """ processor = processor_type(n_clusters=5) @@ -88,16 +90,20 @@ def test_clustering(processor_type: type[clustering.Clustering], data: NDArray[n @pytest.mark.parametrize('processor_type', EXTRA_PROCESSORS + [clustering.DBSCAN]) -def test_embedding_on_distances(processor_type: type[clustering.Clustering], distances: NDArray[numpy.float64]) -> None: +def test_embedding_on_distances( + processor_type: type[clustering.Clustering], + distances: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] +) -> None: """Tests the clustering processors by checking that the 5 found clusters that were determined from pre-computed distances are approximately of similar size. Args: - processor_type (type[clustering.Clustering]): The clustering ``Processor`` that is to be used in the test. - distances (NDArray[numpy.float64]): The pre-computed distances of the data points that are used to cluster the data. + processor_type (type[clustering.Clustering]): The clustering :py:class:`~corelay.processor.base.Processor` that is to be used in the test. + distances (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The pre-computed distances of the data points that are used to cluster the + data. """ - params: dict[str, Any] = {'eps': 0.9} if 'eps' in processor_type.collect(Param) else {} + params: dict[str, typing.Any] = {'eps': 0.9} if 'eps' in processor_type.collect(Param) else {} processor = processor_type(metric='precomputed', **params) computed_clustering = processor(distances) @@ -105,23 +111,24 @@ def test_embedding_on_distances(processor_type: type[clustering.Clustering], dis assert (numpy.unique(computed_clustering, return_counts=True)[1] / 1000).std() < 0.13 -def test_embedding_on_distances_using_agglomerative_clustering(distances: NDArray[numpy.float64]) -> None: - """Tests that the 5 clusters found using the ``AgglomerativeClustering`` class that were determined from pre-computed distances are approximately - of similar size. +def test_embedding_on_distances_using_agglomerative_clustering(distances: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: + """Tests that the 5 clusters found using the :py:class:`~corelay.clustering.AgglomerativeClustering` class that were determined from pre-computed + distances are approximately of similar size. Args: - distances (NDArray[numpy.float64]): The pre-computed distances of the data points that are used to cluster the data. + distances (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The pre-computed distances of the data points that are used to cluster the + data. """ computed_clustering = clustering.AgglomerativeClustering(n_clusters=5, metric='precomputed', linkage='average')(distances) assert (numpy.unique(computed_clustering, return_counts=True)[1] / 1000).std() < 0.05 -def test_dendrogram_creation_with_file_path(tiny_data: NDArray[numpy.float64]) -> None: - """Tests the creation of a dendrogram for the specified data, where the dendrogram image file is specified as a path string. +def test_dendrogram_creation_with_file_path(tiny_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: + """Tests the creation of a dendrogram for the specified data, where the dendrogram image file is specified as a path :py:class:`str`. Args: - tiny_data (NDArray[numpy.float64]): The data to create the dendrogram for. + tiny_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The data to create the dendrogram for. """ output_path = '/tmp/dendrogram.png' @@ -134,11 +141,11 @@ def test_dendrogram_creation_with_file_path(tiny_data: NDArray[numpy.float64]) - os.remove(output_path) -def test_dendrogram_creation_with_file_object(tiny_data: NDArray[numpy.float64]) -> None: +def test_dendrogram_creation_with_file_object(tiny_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: """Tests the creation of a dendrogram for the specified data, where the dendrogram image file is specified as a file object. Args: - tiny_data (NDArray[numpy.float64]): The data to create the dendrogram for. + tiny_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The data to create the dendrogram for. """ output_path = '/tmp/dendrogram.png' diff --git a/tests/unit_tests/corelay/processor/test_embedding.py b/tests/unit_tests/corelay/processor/test_embedding.py index 2612ca5..8826f13 100644 --- a/tests/unit_tests/corelay/processor/test_embedding.py +++ b/tests/unit_tests/corelay/processor/test_embedding.py @@ -1,10 +1,10 @@ -"""A module that contains unit tests for the ``corelay.processor.embedding`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.processor.embedding` module.""" +import typing from importlib import import_module import numpy import pytest -from numpy.typing import NDArray from sklearn.datasets import load_digits from sklearn.metrics.pairwise import euclidean_distances from sklearn.utils import Bunch @@ -31,54 +31,56 @@ @pytest.fixture(name='data', scope='module') -def get_data_fixture() -> NDArray[numpy.float64]: +def get_data_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """A test fixture, which loads a digit dataset that comprises images of two kinds of digits with a shape of (360, 64). Returns: - NDArray[numpy.float64]: Returns the data that was loaded from the dataset. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns the data that was loaded from the dataset. """ digits: Bunch = load_digits(n_class=2) - digits_data: NDArray[numpy.float64] = digits['data'] + digits_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = digits['data'] return digits_data @pytest.fixture(name='distances', scope='module') -def get_distances_fixture(data: NDArray[numpy.float64]) -> NDArray[numpy.float64]: +def get_distances_fixture(data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """A test fixture, which takes the digit dataset loaded in the ``data`` fixture and computes the euclidean distances on it. Args: - data (NDArray[numpy.float64]): The input data that is to be used for the distance computation. This data comes from the ``data`` fixture. + data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The input data that is to be used for the distance computation. This data comes + from the ``data`` fixture. Returns: - NDArray[numpy.float64]: Returns the euclidean distances that were computed on the input data. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns the euclidean distances that were computed on the input data. """ - distance_matrix: NDArray[numpy.float64] = euclidean_distances(data) + distance_matrix: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = euclidean_distances(data) return distance_matrix @pytest.mark.parametrize('processor_type', EMBEDDING_PROCESSORS + EXTRA_PROCESSORS) -def test_embedding(processor_type: type[Processor], data: NDArray[numpy.float64]) -> None: +def test_embedding(processor_type: type[Processor], data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: """Tests the embedding processors on the specified data and checks the dimensions of the result. Args: processor_type (type[Processor]): The type of embedding processor that is to be tested. - data (NDArray[numpy.float64]): The input data that is to be used for the embedding. This data comes from the ``data`` fixture. + data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The input data that is to be used for the embedding. This data comes from the + ``data`` fixture. """ processor = processor_type() - output_embedding: NDArray[numpy.float64] = processor(data) + output_embedding: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = processor(data) assert output_embedding.shape == (360, 2) -def test_eigen_decomposition(distances: NDArray[numpy.float64]) -> None: +def test_eigen_decomposition(distances: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: """Tests the eigen-decomposition of the distances and check the dimensions. Args: - distances (NDArray[numpy.float64]): The input data that is to be used for the eigen-decomposition. This data comes from the ``distances`` - fixture. + distances (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The input data that is to be used for the eigen-decomposition. This data + comes from the ``distances`` fixture. """ eigen_decomposition = embedding.EigenDecomposition(n_eigval=32) @@ -89,12 +91,13 @@ def test_eigen_decomposition(distances: NDArray[numpy.float64]) -> None: @pytest.mark.parametrize('processor_type', EXTRA_PROCESSORS + [embedding.TSNEEmbedding]) -def test_embedding_on_distances(processor_type: type[Processor], distances: NDArray[numpy.float64]) -> None: +def test_embedding_on_distances(processor_type: type[Processor], distances: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: """Tests the embedding processors on pre-computed distances and checks the dimensions. Args: processor_type (type[Processor]): The type of embedding processor that is to be tested. - distances (NDArray[numpy.float64]): The input data that is to be used for the embedding. This data comes from the ``distances`` fixture. + distances (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The input data that is to be used for the embedding. This data comes from + the ``distances`` fixture. """ processor = processor_type(metric='precomputed') diff --git a/tests/unit_tests/corelay/processor/test_flow.py b/tests/unit_tests/corelay/processor/test_flow.py index 88b63c4..d25bb47 100644 --- a/tests/unit_tests/corelay/processor/test_flow.py +++ b/tests/unit_tests/corelay/processor/test_flow.py @@ -1,4 +1,4 @@ -"""A module that contains unit tests for the ``corelay.processor.flow`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.processor.flow` module.""" import pytest @@ -7,7 +7,7 @@ class TestShaper: - """Contains unit tests for the ``Shaper`` class.""" + """Contains unit tests for the :py:class:`~corelay.processor.flow.Shaper` class.""" @staticmethod def test_extract() -> None: @@ -46,7 +46,7 @@ def test_stacked_multiple_levels() -> None: class TestParallel: - """Contains unit tests for the ``Parallel`` class.""" + """Contains unit tests for the :py:class:`~corelay.processor.flow.Parallel` class.""" @staticmethod def test_non_iterable() -> None: @@ -83,12 +83,12 @@ def test_iterable_length_mismatch() -> None: class TestSequential: - """Contains unit tests for the ``Sequential`` class.""" + """Contains unit tests for the:py:class:`~corelay.processor.flow.Sequential` class.""" @staticmethod def test_sequential() -> None: - """Tests that the input to the ``Sequential`` is passed sequentially through the processors as argument in the same order that the child - processors were specified, when the child processors were specified as a keyword argument. + """Tests that the input to the :py:class:`~corelay.processor.flow.Sequential` is passed sequentially through the processors as argument in the + same order that the child processors were specified, when the child processors were specified as a keyword argument. """ sequential = Sequential(children=[FunctionProcessor(processing_function=lambda x, c=c: c + x) for c in 'bcde']) @@ -96,8 +96,8 @@ def test_sequential() -> None: @staticmethod def test_sequential_positional() -> None: - """Tests that the input to the ``Sequential`` is passed sequentially through the processors as argument in the same order that the child - processors were specified, when the child processors were specified as a positional argument. + """Tests that the input to the :py:class:`~corelay.processor.flow.Sequential` is passed sequentially through the processors as argument in the + same order that the child processors were specified, when the child processors were specified as a positional argument. """ sequential = Sequential([FunctionProcessor(processing_function=lambda x, c=c: c + x) for c in 'bcde']) diff --git a/tests/unit_tests/corelay/processor/test_preprocessing.py b/tests/unit_tests/corelay/processor/test_preprocessing.py index 75a1a23..6078a59 100644 --- a/tests/unit_tests/corelay/processor/test_preprocessing.py +++ b/tests/unit_tests/corelay/processor/test_preprocessing.py @@ -1,53 +1,56 @@ -"""A module that contains unit tests for the ``corelay.processor.preprocessing`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.processor.preprocessing` module.""" + +import typing import numpy import pytest -from numpy.typing import NDArray from corelay.processor.preprocessing import Rescale, Resize, Pooling @pytest.fixture(name='no_channels', scope='module') -def get_no_channels_fixture() -> NDArray[numpy.float64]: +def get_no_channels_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """Generates a grayscale image-like array that contains all ones of shape `(number_of_samples, height, width)`. Returns: - NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, height, width)`. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns an image-like array of shape `(number_of_samples, height, width)`. """ return numpy.ones((10, 8, 8)) @pytest.fixture(name='channels_first', scope='module') -def get_channels_first_fixture() -> NDArray[numpy.float64]: +def get_channels_first_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """Generates an image-like array that contains all ones of shape `(number_of_samples, number_of_channels, height, width)`, where the channels come before the image size. Returns: - NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, number_of_channels, height, width)`. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns an image-like array of shape + `(number_of_samples, number_of_channels, height, width)`. """ return numpy.ones((10, 3, 8, 8)) @pytest.fixture(name='channels_last', scope='module') -def get_channels_last_fixture() -> NDArray[numpy.float64]: +def get_channels_last_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """Generates an image-like array that contains all ones of shape `(number_of_samples, height, width, number_of_channels)`, where the channels come last. Returns: - NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, height, width, number_of_channels)`. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns an image-like array of shape + `(number_of_samples, height, width, number_of_channels)`. """ return numpy.ones((10, 8, 8, 3)) @pytest.fixture(name='random_noise', scope='module') -def get_random_noise_fixture() -> NDArray[numpy.float64]: +def get_random_noise_fixture() -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: """Generates a grayscale image-like array that contains all normally distributed noise of shape `(number_of_samples, height, width)`. Returns: - NDArray[numpy.float64]: Returns an image-like array of shape `(number_of_samples, height, width)`. + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns an image-like array of shape `(number_of_samples, height, width)`. """ return numpy.random.normal(0, 1, (10, 8, 8)) @@ -63,6 +66,7 @@ def get_random_noise_fixture() -> NDArray[numpy.float64]: ) def test_rescaling(fixture_name: str, shape: tuple[int, ...], request: pytest.FixtureRequest) -> None: """Tests the rescaling pre-processing processor. + Args: fixture_name (str): The name of the fixture that is to be used for the test. shape (tuple[int, ...]): The expected shape of the output data. @@ -70,9 +74,9 @@ def test_rescaling(fixture_name: str, shape: tuple[int, ...], request: pytest.Fi """ processor = Rescale(scale=0.5, channels_first=fixture_name == 'channels_first') - input_data: NDArray[numpy.float64] = request.getfixturevalue(fixture_name) + input_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = request.getfixturevalue(fixture_name) - output_data: NDArray[numpy.float64] = processor(input_data) + output_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = processor(input_data) assert output_data.shape == shape assert input_data.max() >= output_data.max() diff --git a/tests/unit_tests/corelay/test_base.py b/tests/unit_tests/corelay/test_base.py index 059b073..0ea6d6e 100644 --- a/tests/unit_tests/corelay/test_base.py +++ b/tests/unit_tests/corelay/test_base.py @@ -1,4 +1,4 @@ -"""A module that contains unit tests for the ``corelay.base`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.base` module.""" import pytest @@ -6,39 +6,42 @@ class TestParam: - """Contains unit tests for the ``Param`` class.""" + """Contains unit tests for the :py:class:`~corelay.base.Param` class.""" @staticmethod def test_instantiation() -> None: - """Tests that the ``Param`` class can be instantiated with any data type.""" + """Tests that the :py:class:`~corelay.base.Param` class can be instantiated with any data type.""" Param(object) @staticmethod def test_dtype_not_assigned() -> None: - """Tests that a ``TypeError`` is raised when no data type is provided when instantiating a ``Param`` instance.""" + """Tests that a :py:class:`TypeError` is raised when no data type is provided when instantiating a :py:class:`~corelay.base.Param` instance. + """ with pytest.raises(TypeError): Param() # type: ignore[call-arg] # pylint: disable=no-value-for-parameter @staticmethod def test_dtype_no_type() -> None: - """Tests that a ``TypeError`` is raised when specifying a data type when instantiating a ``Param`` instance that is not a type.""" + """Tests that a :py:class:`TypeError` is raised when specifying a data type when instantiating a :py:class:`~corelay.base.Param` instance that + is not a type. + """ with pytest.raises(TypeError): Param('monkey') # type: ignore[arg-type] @staticmethod def test_dtype_multiple() -> None: - """Tests that the ``Param`` class can be instantiated with multiple data types in a tuple.""" + """Tests that the :py:class:`~corelay.base.Param` class can be instantiated with multiple data types in a tuple.""" param = Param((object, type)) assert param.dtype == (object, type) @staticmethod def test_dtype_single_to_tuple() -> None: - """Tests that, when a single data type was specified when instantiating an instance of ``Param``, that the ``dtype`` property returns a tuple - of types containing a single type. + """Tests that, when a single data type was specified when instantiating an instance of :py:class:`~corelay.base.Param`, that the + :py:attr:`~corelay.plugboard.Slot.dtype` property returns a tuple of types containing a single type. """ param = Param(object) diff --git a/tests/unit_tests/corelay/test_plugboard.py b/tests/unit_tests/corelay/test_plugboard.py index 4abe419..1e548a2 100644 --- a/tests/unit_tests/corelay/test_plugboard.py +++ b/tests/unit_tests/corelay/test_plugboard.py @@ -1,4 +1,4 @@ -"""A module that contains unit tests for the ``corelay.plugboard`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.plugboard` module.""" import pytest @@ -6,40 +6,40 @@ class TestSlot: - """Contains unit tests for the ``Slot`` class.""" + """Contains unit tests for the :py:class:`~corelay.plugboard.Slot` class.""" @staticmethod def test_init() -> None: - """Tests that a ``Slot`` can be instantiated successfully.""" + """Tests that a :py:class:`~corelay.plugboard.Slot` can be instantiated successfully.""" Slot() @staticmethod def test_init_consistent_args() -> None: - """Tests that a ``Slot`` can be instantiated successfully if its arguments are consistent.""" + """Tests that a :py:class:`~corelay.plugboard.Slot` can be instantiated successfully if its arguments are consistent.""" Slot(dtype=int, default=5) @staticmethod def test_init_inconsistent_args() -> None: - """Tests that instantiating a ``Slot`` with inconsistent arguments raises an exception.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Slot` with inconsistent arguments raises an exception.""" with pytest.raises(TypeError): Slot(dtype=str, default=5) @staticmethod def test_init_unknown_args() -> None: - """Tests that instantiating a ``Slot`` with unknown arguments raises an exception.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Slot` with unknown arguments raises an exception.""" with pytest.raises(TypeError): Slot(monkey='banana') @staticmethod def test_init_class_name() -> None: - """Tests that when instantiating a class, the __name__ parameter of a contained ``Slot`` is set accordingly.""" + """Tests that when instantiating a class, the __name__ parameter of a contained :py:class:`~corelay.plugboard.Slot` is set accordingly.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot() """A test slot.""" @@ -48,10 +48,12 @@ class SlotHolder: @staticmethod def test_init_instance_default() -> None: - """Tests that when accessing a ``Slot`` in an instance, where only the default value is set, the default is returned.""" + """Tests that when accessing a :py:class:`~corelay.plugboard.Slot` in an instance, where only the default value is set, the default is + returned. + """ class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(default=42) """A test slot.""" @@ -64,7 +66,7 @@ def test_instance_get() -> None: """Tests that getting a value after setting it succeeds.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int) """A test slot.""" @@ -75,10 +77,10 @@ class SlotHolder: @staticmethod def test_instance_get_no_default() -> None: - """Tests that when accessing a ``Slot`` in an instance, where nothing is set, an exception is raised.""" + """Tests that when accessing a :py:class:`~corelay.plugboard.Slot` in an instance, where nothing is set, an exception is raised.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot() """A test slot.""" @@ -93,7 +95,7 @@ def test_instance_set() -> None: """Tests that everything works alright when not setting a default value, but then setting the value of the object before accessing it.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int) """A test slot.""" @@ -108,7 +110,7 @@ def test_instance_set_wrong_dtype() -> None: """Tests that setting a value with the wrong data type raises an exception.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(str) """A test slot.""" @@ -123,7 +125,7 @@ def test_instance_delete_unchanged() -> None: """Tests that setting a value and deleting it afterwards with a set default value returns the default value""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(int, 42) """A test slot.""" @@ -139,7 +141,7 @@ def test_instance_delete_without_default() -> None: """Tests that setting a value and deleting it afterwards without a default value raises an exception.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(int) """A test slot.""" @@ -155,7 +157,7 @@ def test_class_set_dtype() -> None: """Tests that setting a new data type that is consistent with the data type of the already existing default value succeeds.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=object, default=15) """A test slot.""" @@ -167,7 +169,7 @@ def test_class_set_dtype_inconsistent() -> None: """Tests that setting an new data type that is not consistent with the already existing default value fails.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=object, default=15) """A test slot.""" @@ -180,7 +182,7 @@ def test_class_optional() -> None: """Tests that slots with default values are optional.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -192,7 +194,7 @@ def test_class_not_optional() -> None: """Tests that slots without default values are not optional.""" class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int) """A test slot.""" @@ -201,10 +203,12 @@ class SlotHolder: @staticmethod def test_class_call() -> None: - """Tests that calling a ``Slot`` yields the ``Plug`` associated with the ``Slot``.""" + """Tests that calling a :py:class:`~corelay.plugboard.Slot` yields the :py:class:`~corelay.plugboard.Plug` associated with the + :py:class:`~corelay.plugboard.Slot`. + """ class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -213,10 +217,12 @@ class SlotHolder: @staticmethod def test_class_call_obj() -> None: - """Tests that calling a ``Slot`` with an ``obj`` argument yields a ``Plug`` with the ``obj`` property set to that value.""" + """Tests that calling a :py:class:`~corelay.plugboard.Slot` with an ``obj`` argument yields a :py:class:`~corelay.plugboard.Plug` with the + :py:attr:`~corelay.plugboard.Plug.obj` property set to that value. + """ class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int) """A test slot.""" @@ -225,10 +231,12 @@ class SlotHolder: @staticmethod def test_class_call_default() -> None: - """Tests that calling a ``Slot`` with default-argument yields a ``Plug`` with that default set.""" + """Tests that calling a :py:class:`~corelay.plugboard.Slot` with ``default`` argument yields a :py:class:`~corelay.plugboard.Plug` with the + :py:attr:`~corelay.plugboard.Plug.default` property set. + """ class SlotHolder: - """A test class that holds a single ``Slot``.""" + """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int) """A test slot.""" @@ -237,18 +245,18 @@ class SlotHolder: class TestPlug: - """Contains unit tests for the ``Plug`` class.""" + """Contains unit tests for the :py:class:`~corelay.plugboard.Plug` class.""" @staticmethod def test_init_with_slot_default() -> None: - """Tests that instantiating a ``Plug`` with the default value of a ``Slot`` succeeds.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Plug` with the default value of a :py:class:`~corelay.plugboard.Slot` succeeds.""" slot = Slot(dtype=int, default=10) Plug(slot) @staticmethod def test_init_no_slot_default() -> None: - """Tests that instantiating a ``Plug`` without the default value of a ``Slot`` fails.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Plug` without the default value of a :py:class:`~corelay.plugboard.Slot` fails.""" slot = Slot(dtype=int) @@ -257,8 +265,9 @@ def test_init_no_slot_default() -> None: @staticmethod def test_init_consistent() -> None: - """Tests that instantiating a ``Plug`` with the ``obj`` and ``default`` properties set to values that are consistent with the data type of the - ``Slot`` succeeds. + """Tests that instantiating a :py:class:`~corelay.plugboard.Plug` with the :py:attr:`~corelay.plugboard.Plug.obj` and + :py:attr:`~corelay.plugboard.Plug.default` properties set to values that are consistent with the data type of the + :py:class:`~corelay.plugboard.Slot` succeeds. """ slot = Slot(dtype=int) @@ -266,8 +275,8 @@ def test_init_consistent() -> None: @staticmethod def test_init_consistent_obj() -> None: - """Tests that instantiating a ``Plug`` with the ``obj`` property set to a value that is consistent with the data type of the ``Slot`` - succeeds. + """Tests that instantiating a :py:class:`~corelay.plugboard.Plug` with the :py:attr:`~corelay.plugboard.Plug.obj` property set to a value that + is consistent with the data type of the :py:class:`~corelay.plugboard.Slot` succeeds. """ slot = Slot(dtype=int) @@ -275,8 +284,8 @@ def test_init_consistent_obj() -> None: @staticmethod def test_init_consistent_default() -> None: - """Tests that instantiating a ``Plug`` with the ``default`` property set to a value that is consistent with the data type of the ``Slot`` - succeeds. + """Tests that instantiating a :py:class:`~corelay.plugboard.Plug` with the :py:attr:`~corelay.plugboard.Plug.default` property set to a value + that is consistent with the data type of the :py:class:`~corelay.plugboard.Slot` succeeds. """ slot = Slot(dtype=int) @@ -284,8 +293,8 @@ def test_init_consistent_default() -> None: @staticmethod def test_init_inconsistent_obj() -> None: - """Tests that instantiating a ``Plug`` with the ``obj`` property set to a value that is not consistent with the data type of the ``Slot`` - fails. + """Tests that instantiating a :py:class:`~corelay.plugboard.Plug` with the :py:attr:`~corelay.plugboard.Plug.obj` property set to a value that + is not consistent with the data type of the :py:class:`~corelay.plugboard.Slot` fails. """ slot = Slot(dtype=str) @@ -295,8 +304,8 @@ def test_init_inconsistent_obj() -> None: @staticmethod def test_init_inconsistent_default() -> None: - """Tests that instantiating a ``Plug`` with the ``default`` property set to a value that is not consistent with the data type of the ``Slot`` - fails. + """Tests that instantiating a :py:class:`~corelay.plugboard.Plug` with the :py:attr:`~corelay.plugboard.Plug.default` property set to a value + that is not consistent with the data type of the :py:class:`~corelay.plugboard.Slot` fails. """ slot = Slot(dtype=str) @@ -306,8 +315,10 @@ def test_init_inconsistent_default() -> None: @staticmethod def test_obj_hierarchy_obj() -> None: - """Tests that accessing the ``obj`` property while the ``Slot`` and the ``Plug`` have a default values and the the ``obj`` property of the - ``Plug`` was initialized to a value, should return the value that ``obj`` initialized with. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.obj` property while the :py:class:`~corelay.plugboard.Slot` and the + :py:class:`~corelay.plugboard.Plug` have a default values and the the :py:attr:`~corelay.plugboard.Plug.obj` property of the + :py:class:`~corelay.plugboard.Plug` was initialized to a value, should return the value that :py:attr:`~corelay.plugboard.Plug.obj` + initialized with. """ slot = Slot(dtype=str, default='fallback') @@ -317,8 +328,9 @@ def test_obj_hierarchy_obj() -> None: @staticmethod def test_obj_hierarchy_default() -> None: - """Tests that accessing the ``obj`` property while the ``Slot`` and the ``Plug`` have a default values, should return the ``default`` value - of the ``Plug``. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.obj` property while the :py:class:`~corelay.plugboard.Slot` and the + :py:class:`~corelay.plugboard.Plug` have a default values, should return the :py:attr:`~corelay.plugboard.Plug.default` value of the + :py:class:`~corelay.plugboard.Plug`. """ slot = Slot(dtype=str, default='fallback') @@ -328,7 +340,9 @@ def test_obj_hierarchy_default() -> None: @staticmethod def test_obj_hierarchy_fallback() -> None: - """Tests that accessing the ``obj`` property while the ``Slot`` has a default value, should return the ``default`` value of the ``Slot``.""" + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.obj` property while the :py:class:`~corelay.plugboard.Slot` has a default value, + should return the :py:attr:`~corelay.plugboard.Plug.default` value of the :py:class:`~corelay.plugboard.Slot`. + """ slot = Slot(dtype=str, default='fallback') plug = Plug(slot) @@ -337,8 +351,10 @@ def test_obj_hierarchy_fallback() -> None: @staticmethod def test_default_hierarchy_obj() -> None: - """Tests that accessing the ``default`` property while the ``Slot`` and the ``Plug`` have a default values and the the ``obj`` property of the - ``Plug`` was initialized to a value, should return the ``default`` value of the ``Plug``. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.default` property while the :py:class:`~corelay.plugboard.Slot` and the + :py:class:`~corelay.plugboard.Plug` have a default values and the the :py:attr:`~corelay.plugboard.Plug.obj` property of the + :py:class:`~corelay.plugboard.Plug` was initialized to a value, should return the :py:attr:`~corelay.plugboard.Plug.default` value of the + :py:class:`~corelay.plugboard.Plug`. """ slot = Slot(dtype=str, default='fallback') @@ -348,8 +364,9 @@ def test_default_hierarchy_obj() -> None: @staticmethod def test_default_hierarchy_default() -> None: - """Tests that accessing the ``default`` property while the ``Slot`` and the ``Plug`` have a default values, should return the ``default`` - value of the ``Plug``. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.default` property while the :py:class:`~corelay.plugboard.Slot` and the + :py:class:`~corelay.plugboard.Plug` have a default values, should return the :py:attr:`~corelay.plugboard.Plug.default` value of the + :py:class:`~corelay.plugboard.Plug`. """ slot = Slot(dtype=str, default='fallback') @@ -359,7 +376,8 @@ def test_default_hierarchy_default() -> None: @staticmethod def test_default_hierarchy_fallback() -> None: - """Tests that accessing the ``default`` property while the ``Slot`` has a default value, should return the ``default`` value of the ``Slot``. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.default` property while the :py:class:`~corelay.plugboard.Slot` has a default + value, should return the :py:attr:`~corelay.plugboard.Plug.default` value of the :py:class:`~corelay.plugboard.Slot`. """ slot = Slot(dtype=str, default='fallback') @@ -369,8 +387,10 @@ def test_default_hierarchy_fallback() -> None: @staticmethod def test_fallback_hierarchy_obj() -> None: - """Tests that accessing the ``fallback`` property while the ``Slot`` and the ``Plug`` have a default values and the the ``obj`` property of - the ``Plug`` was initialized to a value, should return the ``default`` value of the ``Slot``. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.fallback` property while the :py:class:`~corelay.plugboard.Slot` and the + :py:class:`~corelay.plugboard.Plug` have a default values and the the :py:attr:`~corelay.plugboard.Plug.obj` property of the + :py:class:`~corelay.plugboard.Plug` was initialized to a value, should return the :py:attr:`~corelay.plugboard.Plug.default` value of the + :py:class:`~corelay.plugboard.Slot`. """ slot = Slot(dtype=str, default='fallback') @@ -380,8 +400,9 @@ def test_fallback_hierarchy_obj() -> None: @staticmethod def test_fallback_hierarchy_default() -> None: - """Tests that accessing the ``fallback`` property while the ``Slot`` and the ``Plug`` have a default values, should return the ``default`` - value of the ``Slot``. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.fallback` property while the :py:class:`~corelay.plugboard.Slot` and the + :py:class:`~corelay.plugboard.Plug` have a default values, should return the :py:attr:`~corelay.plugboard.Plug.default` value of the + :py:class:`~corelay.plugboard.Slot`. """ slot = Slot(dtype=str, default='fallback') @@ -391,7 +412,8 @@ def test_fallback_hierarchy_default() -> None: @staticmethod def test_fallback_hierarchy_fallback() -> None: - """Tests that accessing the ``fallback`` property while the ``Slot`` has a default value, should return the ``default`` value of the ``Slot``. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.fallback` property while the :py:class:`~corelay.plugboard.Slot` has a default + value, should return the :py:attr:`~corelay.plugboard.Plug.default` value of the :py:class:`~corelay.plugboard.Slot`. """ slot = Slot(dtype=str, default='fallback') @@ -401,8 +423,9 @@ def test_fallback_hierarchy_fallback() -> None: @staticmethod def test_hierarchy_none() -> None: - """Tests that accessing the ``default`` and ``fallback`` properties while the neither the ``Slot`` nor the ``Plug`` have a default value, but - the ``obj`` property of the ``Plug`` was initialized to a value returns `None`. + """Tests that accessing the :py:attr:`~corelay.plugboard.Plug.default` and :py:attr:`~corelay.plugboard.Plug.fallback` properties while the + neither the :py:class:`~corelay.plugboard.Slot` nor the :py:class:`~corelay.plugboard.Plug` have a default value, but the + :py:attr:`~corelay.plugboard.Plug.obj` property of the :py:class:`~corelay.plugboard.Plug` was initialized to a value returns :py:obj:`None`. """ slot = Slot(dtype=str) @@ -413,8 +436,9 @@ def test_hierarchy_none() -> None: @staticmethod def test_delete_hierarchy() -> None: - """Tests that deleting the value of the ``obj`` property with the ``Plug`` having a ``default`` set, returns the ``default`` value of the - ``Plug``. + """Tests that deleting the value of the :py:attr:`~corelay.plugboard.Plug.obj` property with the :py:class:`~corelay.plugboard.Plug` having a + :py:attr:`~corelay.plugboard.Plug.default` set, returns the :py:attr:`~corelay.plugboard.Plug.default` value of the + :py:class:`~corelay.plugboard.Plug`. """ slot = Slot(dtype=str) @@ -425,8 +449,9 @@ def test_delete_hierarchy() -> None: @staticmethod def test_delete_hierarchy_last() -> None: - """Tests that deleting the value of the ``default`` property of the ``Plug`` fails, when the ``Slot`` has no ``default`` value and the value - of the ``obj`` property of the ``Plug`` was previously deleted. + """Tests that deleting the value of the :py:attr:`~corelay.plugboard.Plug.default` property of the :py:class:`~corelay.plugboard.Plug` fails, + when the :py:class:`~corelay.plugboard.Slot` has no :py:attr:`~corelay.plugboard.Plug.default` value and the value of the + :py:attr:`~corelay.plugboard.Plug.obj` property of the :py:class:`~corelay.plugboard.Plug` was previously deleted. """ slot = Slot(dtype=str) @@ -438,7 +463,9 @@ def test_delete_hierarchy_last() -> None: @staticmethod def test_obj_set() -> None: - """Tests that setting the ``obj`` property of a ``Plug`` to a value that is consistent with the data type of the ``Slot`` succeeds.""" + """Tests that setting the :py:attr:`~corelay.plugboard.Plug.obj` property of a :py:class:`~corelay.plugboard.Plug` to a value that is + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` succeeds. + """ slot = Slot(dtype=str, default='fallback') plug = Plug(slot) @@ -446,7 +473,9 @@ def test_obj_set() -> None: @staticmethod def test_obj_set_inconsistent() -> None: - """Tests that setting the ``obj`` property of a ``Plug`` to a value that is not consistent with the data type of the ``Slot`` fails.""" + """Tests that setting the :py:attr:`~corelay.plugboard.Plug.obj` property of a :py:class:`~corelay.plugboard.Plug` to a value that is not + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` fails. + """ slot = Slot(dtype=str, default='fallback') plug = Plug(slot) @@ -456,7 +485,8 @@ def test_obj_set_inconsistent() -> None: @staticmethod def test_obj_del() -> None: - """Tests that deleting the value of the ``obj`` property of a ``Plug`` that is associated with a ``Slot`` that has a default value succeeds. + """Tests that deleting the value of the :py:attr:`~corelay.plugboard.Plug.obj` property of a :py:class:`~corelay.plugboard.Plug` that is + associated with a :py:class:`~corelay.plugboard.Slot` that has a default value succeeds. """ slot = Slot(dtype=str, default='fallback') @@ -466,7 +496,9 @@ def test_obj_del() -> None: @staticmethod def test_default_set() -> None: - """Tests that setting the ``default`` property of a ``Plug`` to a value that is consistent with the data type of the ``Slot`` succeeds.""" + """Tests that setting the :py:attr:`~corelay.plugboard.Plug.default` property of a :py:class:`~corelay.plugboard.Plug` to a value that is + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` succeeds. + """ slot = Slot(dtype=str, default='fallback') plug = Plug(slot) @@ -474,7 +506,9 @@ def test_default_set() -> None: @staticmethod def test_default_set_inconsistent() -> None: - """Tests that setting the ``default`` property of a ``Plug`` to a value that is not consistent with the data type of the ``Slot`` fails.""" + """Tests that setting the :py:attr:`~corelay.plugboard.Plug.default` property of a :py:class:`~corelay.plugboard.Plug` to a value that is not + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` fails. + """ slot = Slot(dtype=str, default='fallback') plug = Plug(slot) @@ -484,8 +518,8 @@ def test_default_set_inconsistent() -> None: @staticmethod def test_default_del() -> None: - """Tests that deleting the value of the ``default`` property of a ``Plug`` that is associated with a ``Slot`` that has a ``default`` value - succeeds. + """Tests that deleting the value of the :py:attr:`~corelay.plugboard.Plug.default` property of a :py:class:`~corelay.plugboard.Plug` that is + associated with a :py:class:`~corelay.plugboard.Slot` that has a :py:attr:`~corelay.plugboard.Plug.default` value succeeds. """ slot = Slot(dtype=str, default='fallback') @@ -496,7 +530,9 @@ def test_default_del() -> None: @staticmethod def test_not_optional() -> None: - """Tests that if neither the ``Plug`` nor the ``Slot`` have a ``default`` value, the ``obj`` value is not optional.""" + """Tests that if neither the :py:class:`~corelay.plugboard.Plug` nor the :py:class:`~corelay.plugboard.Slot` have a + :py:attr:`~corelay.plugboard.Plug.default` value, the :py:attr:`~corelay.plugboard.Plug.obj` value is not optional. + """ slot = Slot(dtype=str) plug = Plug(slot, obj='obj') @@ -505,7 +541,9 @@ def test_not_optional() -> None: @staticmethod def test_optional() -> None: - """Tests that if the ``default`` value of the ``Plug`` is set, the ``obj`` property is optional.""" + """Tests that if the :py:attr:`~corelay.plugboard.Plug.default` value of the :py:class:`~corelay.plugboard.Plug` is set, the + :py:attr:`~corelay.plugboard.Plug.obj` property is optional. + """ slot = Slot(dtype=str) plug = Plug(slot, default='default', obj='obj') @@ -514,7 +552,9 @@ def test_optional() -> None: @staticmethod def test_slot_set_consistent() -> None: - """Tests that assigning a new ``Slot`` to a ``Plug``, that has the correct data type succeeds.""" + """Tests that assigning a new :py:class:`~corelay.plugboard.Slot` to a :py:class:`~corelay.plugboard.Plug`, that has the correct data type + succeeds. + """ slot = Slot(dtype=object) plug = Plug(slot, obj='default') @@ -522,7 +562,9 @@ def test_slot_set_consistent() -> None: @staticmethod def test_slot_set_inconsistent() -> None: - """Tests that assigning a new ``Slot`` to a ``Plug``, that does not have the correct data type fails.""" + """Tests that assigning a new :py:class:`~corelay.plugboard.Slot` to a :py:class:`~corelay.plugboard.Plug`, that does not have the correct + data type fails. + """ slot = Slot(dtype=object) plug = Plug(slot, obj='default') @@ -532,8 +574,9 @@ def test_slot_set_inconsistent() -> None: @staticmethod def test_slot_set_no_default() -> None: - """Tests that assigning a new ``Slot`` to a ``Plug``, that does not have a default value, and neither the original ``Slot`` nor the ``Plug`` - have a ``default`` or ``obj`` value fails. + """Tests that assigning a new :py:class:`~corelay.plugboard.Slot` to a :py:class:`~corelay.plugboard.Plug`, that does not have a default + value, and neither the original :py:class:`~corelay.plugboard.Slot` nor the :py:class:`~corelay.plugboard.Plug` have a + :py:attr:`~corelay.plugboard.Plug.default` or :py:attr:`~corelay.plugboard.Plug.obj` value fails. """ slot = Slot(dtype=object, default='fallback') @@ -544,34 +587,36 @@ def test_slot_set_no_default() -> None: class TestPlugboard: - """Contains unit tests for the ``Plugboard`` class.""" + """Contains unit tests for the :py:class:`~corelay.plugboard.Plugboard` class.""" @staticmethod def test_init() -> None: - """Tests that instantiating a ``Plugboard`` without anything set succeeds.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Plugboard` without anything set succeeds.""" Plugboard() @staticmethod def test_init_unknown_kwargs() -> None: - """Tests that instantiating a ``Plugboard`` with unknown keyword arguments fails.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Plugboard` with unknown keyword arguments fails.""" with pytest.raises(TypeError): Plugboard(stuff=19) @staticmethod def test_init_args() -> None: - """Tests that instantiating a ``Plugboard`` with any positional arguments fails.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Plugboard` with any positional arguments fails.""" with pytest.raises(TypeError): Plugboard(19) # type: ignore[call-arg] # pylint: disable=too-many-function-args @staticmethod def test_init_assign() -> None: - """Tests that instantiating a ``Plugboard`` with keyword arguments identifying slots, sets the values of those slots.""" + """Tests that instantiating a :py:class:`~corelay.plugboard.Plugboard` with keyword arguments identifying slots, sets the values of those + slots. + """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -582,10 +627,12 @@ class MyPlugboard(Plugboard): @staticmethod def test_default_get() -> None: - """Tests that accessing the default value of a ``Slot`` in a ``Plugboard`` that has an explicit ``obj`` value succeeds.""" + """Tests that accessing the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` that has an + explicit :py:attr:`~corelay.plugboard.Plug.obj` value succeeds. + """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -596,10 +643,12 @@ class MyPlugboard(Plugboard): @staticmethod def test_default_set() -> None: - """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` that is consistent with the data type of the ``Slot`` succeeds.""" + """Tests that setting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` that is + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` succeeds. + """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -611,12 +660,12 @@ class MyPlugboard(Plugboard): @staticmethod def test_default_set_dict() -> None: - """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` that is consistent with the data type of the ``Slot`` using a - dictionary succeeds. + """Tests that setting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` that is + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` using a :py:class:`dict` succeeds. """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -628,12 +677,12 @@ class MyPlugboard(Plugboard): @staticmethod def test_default_set_dict_wrong() -> None: - """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` using anything but a dictionary or the attribute accessor of the - ``Slot`` fails. + """Tests that setting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` using anything + but a :py:class:`dict` or the attribute accessor of the :py:class:`~corelay.plugboard.Slot` fails. """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -645,10 +694,12 @@ class MyPlugboard(Plugboard): @staticmethod def test_default_del() -> None: - """Tests that deleting the default value of a ``Slot`` in a ``Plugboard`` succeeds and reverts back to the default value of the ``Slot``.""" + """Tests that deleting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` succeeds and + reverts back to the default value of the :py:class:`~corelay.plugboard.Slot`. + """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -661,10 +712,12 @@ class MyPlugboard(Plugboard): @staticmethod def test_default_set_influence_obj() -> None: - """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` does not influence the value of the ``Slot``.""" + """Tests that setting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` does not + influence the value of the :py:class:`~corelay.plugboard.Slot`. + """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -676,10 +729,11 @@ class MyPlugboard(Plugboard): @staticmethod def test_default_dir() -> None: - """Tests that the ``default`` property of the plugboard is a dictionary, that contains entries for all of its slots.""" + """Tests that the :py:attr:`~corelay.plugboard.Plug.default` property of the plugboard is a :py:class:`dict`, that contains entries for all of + its slots.""" class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -690,12 +744,13 @@ class MyPlugboard(Plugboard): @staticmethod def test_update_defaults() -> None: - """Tests that setting the default value of a ``Slot`` in a ``Plugboard`` that is consistent with the data type of the ``Slot`` using the - ``update_defaults`` method succeeds. + """Tests that setting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` that is + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` using the + :py:meth:`Plugboard.update_defaults ` method succeeds. """ class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" @@ -707,10 +762,10 @@ class MyPlugboard(Plugboard): @staticmethod def test_reset_defaults() -> None: - """Tests that resetting the default value of a ``Slot`` in a ``Plugboard`` succeeds.""" + """Tests that resetting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` succeeds.""" class MyPlugboard(Plugboard): - """A test plugboard that holds a single ``Slot``.""" + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" my_slot = Slot(dtype=int, default=15) """A test slot.""" diff --git a/tests/unit_tests/corelay/test_tracker.py b/tests/unit_tests/corelay/test_tracker.py index f945016..b317927 100644 --- a/tests/unit_tests/corelay/test_tracker.py +++ b/tests/unit_tests/corelay/test_tracker.py @@ -1,4 +1,4 @@ -"""A module that contains unit tests for the ``corelay.tracker`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.tracker` module.""" import pytest @@ -7,14 +7,14 @@ @pytest.fixture(name='tracker_type', scope='module') def get_tracked_fixture() -> type[Tracker]: - """Creates a sub-class of ``Tracker`` with some attributes. + """Creates a sub-class of :py:class:`~corelay.tracker.Tracker` with some attributes. Returns: - type[Tracker]: A sub-class of ``Tracker`` with some attributes. + type[Tracker]: Returns a sub-class of :py:class:`~corelay.tracker.Tracker` with some attributes. """ class SubTracked(Tracker): - """A sub-class of ``Tracker`` with some attributes.""" + """A sub-class of :py:class:`~corelay.tracker.Tracker` with some attributes.""" attr_1 = 42 attr_2 = 'apple' @@ -31,7 +31,7 @@ def get_values_fixture() -> dict[str, int | str | type]: """Generates a list of values, that can be used to test the collection of values. Returns: - dict[str, int | str | type]: Returns a dictionary with values that can be used to test the collection of values. + dict[str, int | str | type]: Returns a :py:class:`dict` with values that can be used to test the collection of values. """ result: dict[str, int | str | type] = { @@ -50,7 +50,7 @@ def get_attributes_fixture() -> dict[str, str]: """Generates some new values for tracked parameters. Returns: - dict[str, str]: Returns a dictionary with new values for tracked parameters. + dict[str, str]: Returns a :py:class:`dict` with new values for tracked parameters. """ result: dict[str, str] = { @@ -83,7 +83,7 @@ def get_instance_fixture(tracker_type: type[Tracker], attributes: dict[str, str] class TestTracker: - """Contains unit tests for the ``Tracker`` class.""" + """Contains unit tests for the :py:class:`~corelay.tracker.Tracker` class.""" @staticmethod def test_collect(tracker_type: type[Tracker]) -> None: diff --git a/tests/unit_tests/corelay/test_utils.py b/tests/unit_tests/corelay/test_utils.py index 9e68b77..6143230 100644 --- a/tests/unit_tests/corelay/test_utils.py +++ b/tests/unit_tests/corelay/test_utils.py @@ -1,4 +1,4 @@ -"""A module that contains unit tests for the ``corelay.utils`` module.""" +"""A module that contains unit tests for the :py:mod:`corelay.utils` module.""" import pytest @@ -46,7 +46,7 @@ def test_conditional_import_of_multiple_functions() -> None: class TestZipEqual: - """Contains unit tests for the ``zip_equal`` function.""" + """Contains unit tests for the :py:func:`~corelay.utils.zip_equal` function.""" @staticmethod def test_equal_length() -> None: From ac1fd44c59c40ef2688b1c68843406bc30f91f32 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Tue, 27 May 2025 13:47:29 +0200 Subject: [PATCH 12/19] Cleaned Up The CSpell Dictionary Removed unused words from the CSpell dictionary to keep it clean and relevant. --- docs/source/api-reference/index.rst | 2 +- tests/linters/cspell/.cspell.json | 42 ----------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/docs/source/api-reference/index.rst b/docs/source/api-reference/index.rst index b92a762..cb0d88a 100644 --- a/docs/source/api-reference/index.rst +++ b/docs/source/api-reference/index.rst @@ -6,7 +6,7 @@ The following section provides an exhaustive documentation of CoRelAy's constitu .. autosummary:: :toctree: - :nosignatures: + :signatures: none :recursive: corelay.base diff --git a/tests/linters/cspell/.cspell.json b/tests/linters/cspell/.cspell.json index 97ec9d4..900d565 100644 --- a/tests/linters/cspell/.cspell.json +++ b/tests/linters/cspell/.cspell.json @@ -80,13 +80,11 @@ "absspectral", "addopts", "adfg", - "agglo", "Anders", "Anson", "arccos", "automodule", "autosummary", - "basepython", "Batchelder", "bcde", "bibfiles", @@ -103,7 +101,6 @@ "chebyshev", "Chormai", "chr5tphr", - "chrstphr", "cityblock", "clusterings", "codezombiech", @@ -111,7 +108,6 @@ "copybutton", "CoRelAy", "coveragerc", - "csints", "datatemplates", "DBSCAN", "dcba", @@ -124,14 +120,10 @@ "dvipng", "edcba", "eigendecomposition", - "eigenstuff", "eigsh", "eigval", - "eigvals", "eigvec", - "Envlist", "envname", - "envsitepackagesdir", "eprintclass", "eprinttype", "eventdate", @@ -144,25 +136,17 @@ "Funcache", "genindex", "getfixturevalue", - "getrev", "Grégoire", - "hashval", "hdbscan", "hellinger", "histogramdd", - "htmlcov", "htmlvalidate", "ifmat", "imgmath", "incollection", - "inlinevar", "intersphinx", - "iobj", "issn", "issuetitle", - "iterhash", - "iterread", - "iterwrite", "jacc", "jaccard", "jensenshannon", @@ -173,64 +157,46 @@ "Kuczmarski", "kulczynski", "kulsinski", - "Laplacians", "Lapuschkin", "lecode", "lextudio", "linalg", - "linestart", - "linestop", - "ller", "LNCS", "Luxburg", "mahal", "maintitle", "maintitleaddon", "mathjax", - "membertype", - "metacls", "metrohash", "mgrid", "minkowski", "modindex", - "modname", - "modpath", "Montavon", "moveaxis", - "msvs", "Müller", - "mypackage", "ndarray", - "ndarrays", "Neumann", "nitpicky", - "nosignatures", "notest", - "nsamples", "nsri", "orcid", - "overgeneral", "pagetotal", "parseable", "Pattarawat", "pdist", - "pdistance", "pearsonr", "Pickler", "Plugboards", "pnorm", "posargs", - "prepreprocess", "pubstate", "pybtex", "pydoclint", - "pygtk", "pylintrc", "randn", "rcfile", "reftypes", "regionref", - "ritwickdey", "rodolphebarbanneau", "rogerstanimoto", "russellrao", @@ -250,20 +216,12 @@ "sqeuclidean", "squareform", "SSIM", - "submod", - "subname", - "subnames", - "symmetrix", "synset", "tamasfe", "testpaths", "texlive", "toctree", "todense", - "topmodulename", - "toxinidir", - "toxworkdir", - "trange", "TSNE", "typehints", "ufunc", From 36665677b1f9d331070a0c9acdb26f8dce989264 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Tue, 27 May 2025 13:58:00 +0200 Subject: [PATCH 13/19] Automated the Publishing of CoRelAy to PyPI Added a new GitHub Actions workflow, which builds the project and publishes it to PyPI. This workflow is triggered when GitHub release for a new version is created. --- .github/workflows/deploy.yaml | 60 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 1 + 2 files changed, 61 insertions(+) create mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..6c76e7c --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,60 @@ + +# This workflow will build the CoRelAy project and publish the artifacts to PyPI when a GitHub release is created +name: CoRelAy Continuous Deployment + +# This workflow will run when a new release is created on GitHub, the process works like this: +# 1) For each milestone multiple issues are created +# 2) For each issue a new branch is branched off from the develop branch (which itself is branched off from main) +# 3) When the issue is resolved, a pull request is created to merge the issue branch into the develop branch +# 4) Once all issues for a milestone are resolved, a pull request is created to merge the develop branch into the main branch +# 5) When the pull request is merged, a new release is created +# 6) This workflow will then be triggered and the CoRelAy project will be built and published to PyPI +on: + release: + types: + - created + +# This workflow contains a single job for building and publishing the CoRelAy project +jobs: + + # Builds and publishes the CoRelAy project + build-and-publish: + + # The job will run on the latest version of Ubuntu + name: Build and Publish the CoRelAy Project + runs-on: ubuntu-latest + + # Gives the job permission to write the ID token, which is required for trusted publishing; trusted publishing is a way to publish packages to a + # package index (like PyPI) without needing to store credentials in the repository; trusted publishing is set up beforehand and during the + # publishing step and allows the job to automatically authenticate with the package index to retrieve a JWT token for publishing the package; the + # write permission is allows GitHub to generate an OIDC token, which is used to authenticate with the package index and retrieve the JWT token + permissions: + id-token: write + + # Specifies that we want to deploy to PyPI + environment: + name: pypi + url: https://pypi.org/p/virelay + + # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing Python, 4) + # installing the dependencies of CoRelAy, 5) building the CoRelAy project, and 6) publishing the CoRelAy project + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.6.13 + - name: Install Python + run: uv python install 3.13.2 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + - name: Install CoRelAy and its Dependencies + run: uv --directory source/backend sync --all-extras + - name: Build the CoRelAy Project + run: uv --directory source/backend build + - name: Publish the CoRelAy Project to PyPI + run: uv --directory source/backend publish diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2b605..a9f944c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ - Removed the GitHub Actions workflow matrix configurations for Python 3.7, as it is no longer supported by the project. - Added a job to the GitHub Actions workflow, which spell-checks the repository. - The GitHub Actions workflow now runs on pull requests and merges to the `main` and the `develop` branches. The workflow was previously only ran on pull requests and merges to the `main` branch. This was changed, because every feature branch that is to be merged into `develop` should be tested and linted before it is merged. Otherwise, build, test, and linting errors would only be detected just before the release of a new version, when the `develop` branch is merged into `main`. +- Added a new GitHub Actions workflow, which builds the project and publishes it to PyPI. This workflow is triggered when GitHub release for a new version is created. - The configuration for the GitLab CI, which was stored in the `.gitlab-ci.yml` file, was removed. The project is no longer being hosted on GitLab, and the CI configuration is no longer needed. ### Documentation Updates in v0.3.0 From d35900760b9c2bf933db50510e244517be204080 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Tue, 27 May 2025 15:28:45 +0200 Subject: [PATCH 14/19] Added a Deprecation Warning Now, when the old syntax for declaring slots is used, a deprecation warning will be raised. Unfortunately, Python filters out deprecation warnings by default, so developers will need to add "-W all" to command line arguments when running Python to see the warning. Hopefully, developers will use a development environment that automatically enables all warnings for them. --- CHANGELOG.md | 1 + source/corelay/tracker.py | 50 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f944c..f521a17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ - Slots are now defined by declaring a class attribute with a type hint using the `Annotated` type. This is a type that allows to specify a type and additional metadata. For example, a string `Param` can be defined as follows: `param: Annotated[str, Param(str, 'Default value')]`. - This way, MyPy can infer the runtime type of the attribute and the metadata is used to define the `Slot`. - The old syntax is still supported, but it is no longer recommended and may be removed in the future. + - A deprecation warning is raised when using the old syntax, so that users can easily find and fix the code that uses the old syntax. ### CI/CD Updates in v0.3.0 diff --git a/source/corelay/tracker.py b/source/corelay/tracker.py index a08d458..dda97b9 100644 --- a/source/corelay/tracker.py +++ b/source/corelay/tracker.py @@ -5,6 +5,7 @@ import collections import typing from abc import ABCMeta +from warnings import warn class MetaTracker(ABCMeta): @@ -93,7 +94,7 @@ def __new__(mcs, class_name: str, base_classes: tuple[type, ...], class_attribut MetaTracker: Returns the new class with the :py:attr:`Tracker.__tracked__` attribute. """ - # Retrieves the class attributes that are not special "dunder" attributes, like __class__, i.e., any class attributes that is not enclosed in + # Retrieves the class attributes that are not special "dunder" attributes, like __class__, i.e., any class attributes that are not enclosed in # double underscores (this is the classical way in which slots can be declared) tracked_class_attributes: collections.OrderedDict[str, typing.Any] = collections.OrderedDict( (attribute_name, attribute_value) @@ -101,7 +102,52 @@ def __new__(mcs, class_name: str, base_classes: tuple[type, ...], class_attribut if not (attribute_name[:2] + attribute_name[-2:]) == '____' ) - # Retrieves the declared class attributes, which were declared using Annotated (this is the new way in which slots can be declared) + def is_slot(object_to_check: typing.Any) -> bool: + """Checks if the given object is either a :py:class:`~corelay.plugboard.Slot` or of a type that directly or indirectly derives from it. + This function is needed, because the module containing the :py:class:`~corelay.plugboard.Slot` class imports + :py:class:`~corelay.tracker.Tracker`. Therefore, directly importing the :py:class:`~corelay.plugboard.Slot` class here would cause a + circular import. + + Args: + object_to_check (typing.Any): The object to check if it is a :py:class:`~corelay.plugboard.Slot`. + + Returns: + bool: Returns :py:obj:`True` if the given object is either a :py:class:`~corelay.plugboard.Slot` or derives directly or indirectly + from it. Otherwise :py:obj:`False` is returned. + """ + + if object_to_check is None: + return False + + classes_to_check: list[type] = [object_to_check.__class__] + classes_checked: list[type] = [] + while classes_to_check: + current_class: type = classes_to_check.pop(0) + + if current_class.__name__ == 'Slot': + return True + classes_checked.append(current_class) + + if hasattr(current_class, '__bases__'): + for base_class in current_class.__bases__: + if base_class not in classes_checked and base_class not in classes_to_check: + classes_to_check.append(base_class) + + return False + + # Since the old syntax of declaring slots is deprecated, but still supported for the time being, a deprecation warning is issued + for attribute_name, attribute_value in tracked_class_attributes.items(): + if is_slot(attribute_value): + warn( + f'The {attribute_value.__class__.__name__} "{attribute_name}" was declared using the old syntax of declaring slots, which is ' + 'deprecated. This syntax is currently still supported, but it will be removed in a future version of CoRelAy. Please refer to ' + 'the migration guide to find out why this syntax was deprecated and how to update your code: ' + 'https://corelay.readthedocs.io/en/latest/migration-guide/migrating-from-v0.2-to-v0.3.html.', + DeprecationWarning + ) + + # Retrieves the declared class attributes, which were declared using Annotated and are are not special "dunder" attributes, like __class__, + # i.e., any class attributes that are not enclosed in double underscores (this is the new way in which slots can be declared) tracked_declared_class_attributes: collections.OrderedDict[str, typing.Any] = collections.OrderedDict() if '__annotations__' in class_attributes: for attribute_name, attribute_value in class_attributes['__annotations__'].items(): From 381bff33409c55e4136984307a7ce3321e64de87 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Wed, 28 May 2025 18:30:30 +0200 Subject: [PATCH 15/19] Increased the Test Coverage to 100% Added new unit tests and updated existing ones to increase the test coverage of the CoRelAy codebase to 100%. The dependencies of the CoRelAy package and of the Node.js-based linters were updated to their latest versions, since this is the last commit before the release. This was done to ensure that everything is up-to-date and works as expected. The UMAP dependency is now installed directly from the GitHub repository, since the latest version of UMAP is not yet available on PyPI, although already being available since February 2025. The new version fixes some deprecation warnings of third-party libraries, which are used by CoRelAy, thus ensuring that CoRelAy does not raise any deprecation warnings itself. Pybtex, a dependency of the BibTeX extension for Sphinx, which is used to generate the bibliography in the documentation also causes a deprecation warning when building the documentation as it is still using the deprecated "pkg_resources" module for loading plugins. The version of Pybtex available on PyPI is 0.24.0, which is still using the "pkg_resources" module. The problem has already been fixed in the development version, but it is not yet released. Therefore, the development version is installed directly from the Git repository. If and when a successor to 0.24.0 is released, this dependency can be removed again. The optional dependencies of the CoRelAy package are now also included in the "testing" and "linting" dependency groups, so that CoRelAy can be tested and linted with the optional dependencies installed. Some minor changes were made to the codebase: - The "Shaper" flow processor was extended to support dictionaries and string indices. It seems like, these features were already expected to be present, but they were prevented by some minor mistakes: Dictionaries were not supported, because the "Shaper" processor tested for the type of the input data by testing if they implemented the "Sequence" protocol, which is not the case for dictionaries. Instead, dictionaries implement the "Mapping" protocol, which is now also tested for. String indices were not supported, because the indices were tested for implementing the "Sequence" protocol, which is also the case for strings. This meant, that each character of a string was treated as a separate index, which is not the intended behavior. Now, it is also checked if the indices are not strings. - The stack level of the warning, which is raised when the old "Slot" syntax is used was increased to 2, so that the user can see the location of the code that is using the old syntax, instead of seeing the location of the warning itself. - The "get_lambda_expression_source_code" function was updated to fixed some use-cases in which the source code of the lambda expression was not correctly retrieved. - The function types that "Slot" and "Plug" support has been expanded to include some types that were previously not supported. - The "get_fully_qualified_name" function was also updated to support these new function types. Some further code changes were made to make the code more testable. This only includes changes that remove checking code that is never executed. For example, in the "MetaTracker" meta class, the check if the class has an attribute "__bases__" was removed, since this attribute is always present in Python classes. Also, some minor adjustments were made to the docstrings of the codebase to include more information or correct the information already present. Finally, some bugs in the codebase were fixed, that were discovered during the creation of the new unit tests: - Fixed a bug in the "RadialBasisFunction" affinity processor: The formula for the Radial Basis Function (RBF) kernel was incorrect. The distance matrix was not squared resulting in the wrong formula K(d) = exp(-d/(2sigma^ 2)) instead of the correct formula K(d) = exp(-(d^2)/(2sigma^2))$. This was fixed by squaring the distance matrix. - Fixed a bug in the "Histogram" processor: The processor was using the "numpy.histogramdd" function, which computes a multivariate histogram, but the processor was meant to compute a histogram over the channels of the input data, for which the "numpy.histogramdd" function is not suitable. Instead, the "numpy.histogram" function is now used in conjunction with the "numpy.stack" function to compute the histogram over the channels of the input data. Also, the "Histogram" processor was not able to deal with channel-last data, which is now supported. The "Histogram" processor created in the "virelay_analysis.py" example script was also not working as expected: Although it was using the "numpy.histogram" function, it did not account for its return type, which is a tuple containing the histogram and the bin edges. Since we now have a working implementation of the "Histogram" processor, the example script was updated to use it. - The root directory specified in the CSpell script was not correct, which meant that not all files were checked for spelling errors. This was fixed by updating the root directory to the correct one. --- .github/workflows/deploy.yaml | 2 +- .github/workflows/tests.yml | 10 +- CHANGELOG.md | 4 + docs/examples/virelay_analysis.py | 28 +- .../contributing-code-or-documentation.rst | 2 +- docs/source/getting-started/installation.rst | 2 +- source/corelay/base.py | 9 +- source/corelay/io/storage.py | 42 +- source/corelay/pipeline/base.py | 51 +- source/corelay/plugboard.py | 55 +- source/corelay/processor/affinity.py | 2 +- source/corelay/processor/flow.py | 19 +- source/corelay/processor/laplacian.py | 2 +- source/corelay/processor/preprocessing.py | 71 +- source/corelay/tracker.py | 12 +- source/corelay/utils.py | 233 +++-- source/pyproject.toml | 53 +- source/uv.lock | 446 +++++---- tests/linters/.pycodestyle | 11 + tests/linters/.pylintrc | 29 +- tests/linters/cspell/.cspell.json | 8 + tests/linters/cspell/package-lock.json | 906 ++++++++---------- tests/linters/cspell/package.json | 4 +- tests/linters/markdownlint/package-lock.json | 114 +-- tests/linters/markdownlint/package.json | 4 +- tests/unit_tests/.coveragerc | 3 + tests/unit_tests/corelay/io/test_hashing.py | 203 ++++ tests/unit_tests/corelay/io/test_storage.py | 217 ++++- .../unit_tests/corelay/pipeline/test_base.py | 221 ++++- .../corelay/pipeline/test_spectral.py | 1 + .../corelay/processor/test_affinity.py | 62 ++ .../unit_tests/corelay/processor/test_base.py | 22 + .../corelay/processor/test_clustering.py | 11 + .../corelay/processor/test_distance.py | 27 + .../corelay/processor/test_embedding.py | 28 +- .../unit_tests/corelay/processor/test_flow.py | 34 + .../corelay/processor/test_laplacian.py | 67 ++ .../corelay/processor/test_preprocessing.py | 67 +- tests/unit_tests/corelay/test_base.py | 13 + tests/unit_tests/corelay/test_plugboard.py | 155 ++- tests/unit_tests/corelay/test_tracker.py | 107 ++- tests/unit_tests/corelay/test_utils.py | 197 +++- 42 files changed, 2456 insertions(+), 1098 deletions(-) create mode 100644 tests/unit_tests/corelay/io/test_hashing.py create mode 100644 tests/unit_tests/corelay/processor/test_affinity.py create mode 100644 tests/unit_tests/corelay/processor/test_distance.py create mode 100644 tests/unit_tests/corelay/processor/test_laplacian.py diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 6c76e7c..dfeace7 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -53,7 +53,7 @@ jobs: with: node-version: '22.14.0' - name: Install CoRelAy and its Dependencies - run: uv --directory source/backend sync --all-extras + run: uv --directory source/backend sync - name: Build the CoRelAy Project run: uv --directory source/backend build - name: Publish the CoRelAy Project to PyPI diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ecaea26..ef8336d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: - name: Install Python run: uv python install ${{ matrix.python-version }} - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --all-extras --dev + run: uv --directory source sync --dev - name: Run Unit Tests run: uv --directory source run tox run -e ${{ matrix.tox-environment }} @@ -71,7 +71,7 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --all-extras --dev + run: uv --directory source sync --dev - name: Run PyLint Linter run: uv --directory source run tox run -e pylint @@ -95,7 +95,7 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --all-extras --dev + run: uv --directory source sync --dev - name: Run PyLint Linter run: uv --directory source run tox run -e pycodestyle @@ -119,7 +119,7 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --all-extras --dev + run: uv --directory source sync --dev - name: Run PyLint Linter run: uv --directory source run tox run -e pydoclint @@ -143,7 +143,7 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --all-extras --dev + run: uv --directory source sync --dev - name: Run PyLint Linter run: uv --directory source run tox run -e mypy diff --git a/CHANGELOG.md b/CHANGELOG.md index f521a17..b14f3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Functions in SciKit Learn and SciKit Image that allow single-channel or multi-channel images used to have a boolean `multichannel` constructor parameter. This parameter was deprecated in favor of specifying the axis of the channels using a new `channel_axis` parameter. A value of `None` indicates that the image is single-channel. The usage of `multichannel` was removed and replaced with the appropriate `channel_axis` parameter. - The SciKit Learn implementation of the t-SNE dimensionality reduction algorithm, represented by the `TSNE` class, now uses PCA as the default initialization method instead of a random initialization. The `precomputed` metric is not compatible with PCA initialization, which was used in CoRelAy, which caused an exception to be raised. For this reason, the initialization method is now explicitly set to `random` in the `TSNE` class. - The `AgglomerativeClustering` class in SciKit Learn now uses the `metric` constructor parameter instead of `affinity` to specify the distance metric. Uses of the `affinity` parameter were replaced with the `metric` parameter. + - Brought the coverage of the unit tests to 100%. - The unit tests were moved from the `tests` folder to `tests/unit_tests`, which clears up space for other test files. - Updated the linting of the project: - First of all, the Flake8 linter was removed. It is a wrapper for the PyFlakes and PyCodeStyle linters, and Ned Batchelder's McCabe script, which is used to compute the McCabe complexity. PyFlakes is a static code analyzer similar to PyLint, but way less useful. McCabe complexity is a useful metric, but it is not currently used by the project. For this reason, the Flake8 linter was removed and PyCodeStyle is now directly used instead. @@ -67,6 +68,9 @@ - This way, MyPy can infer the runtime type of the attribute and the metadata is used to define the `Slot`. - The old syntax is still supported, but it is no longer recommended and may be removed in the future. - A deprecation warning is raised when using the old syntax, so that users can easily find and fix the code that uses the old syntax. +- Fixed a bug in the `RadialBasisFunction` affinity processor: The formula for the Radial Basis Function (RBF) kernel was incorrect. The distance matrix was not squared resulting in the wrong formula $K(d) = e^{-\frac{d}{2\sigma^2}}$ instead of the correct formula $K(d) = e^{-\frac{d^2}{2\sigma^2}}$. This was fixed by squaring the distance matrix. +- Fixed a bug in the `Histogram` processor: The processor was using the `numpy.histogramdd` function, which computes a multivariate histogram, but the processor was meant to compute a histogram over the channels of the input data, for which the `numpy.histogramdd` function is not suitable. Instead, the `numpy.histogram` function is now used in conjunction with the `numpy.stack` function to compute the histogram over the channels of the input data. Also, the `Histogram` processor was not able to deal with channel-last data, which is now supported. The `Histogram` processor created in the `virelay_analysis.py` example script was also not working as expected: Although it was using the `numpy.histogram` function, it did not account for its return type, which is a tuple containing the histogram and the bin edges. Since we now have a working implementation of the `Histogram` processor, the example script was updated to use it. +- The `Shaper` flow processor was extended to support dictionaries and string indices. It seems like, these features were already expected to be present, but they were prevented by some minor mistakes: Dictionaries were not supported, because the `Shaper` processor tested for the type of the input data by testing if they implemented the `Sequence` protocol, which is not the case for dictionaries. Instead, dictionaries implement the `Mapping` protocol, which is now also tested for. String indices were not supported, because the indices were tested for implementing the `Sequence` protocol, which is also the case for strings. This meant, that each character of a string was treated as a separate index, which is not the intended behavior. Now, it is also checked if the indices are not strings. ### CI/CD Updates in v0.3.0 diff --git a/docs/examples/virelay_analysis.py b/docs/examples/virelay_analysis.py index f01c920..eb280fc 100644 --- a/docs/examples/virelay_analysis.py +++ b/docs/examples/virelay_analysis.py @@ -22,6 +22,7 @@ from corelay.processor.distance import SciPyPDist from corelay.processor.embedding import TSNEEmbedding, UMAPEmbedding, EigenDecomposition from corelay.processor.flow import Sequential, Parallel +from corelay.processor.preprocessing import Histogram class Flatten(Processor): @@ -94,33 +95,6 @@ def function(self, data: typing.Any) -> typing.Any: return input_data / input_data.sum(self.axes, keepdims=True) -class Histogram(Processor): - """Represents a :py:class:`~corelay.processor.base.Processor`, which computes a histogram over its input data.""" - - bins: Annotated[int, Param(int, 256)] - """A parameter of the processor, which determines the number of bins that are used to compute the histogram.""" - - def function(self, data: typing.Any) -> typing.Any: - """Computes histograms over the specified input data. One histogram is computed for each channel and each sample in a batch of input data. - - Args: - data (typing.Any): The input data over which the histograms are to be computed. - - Returns: - typing.Any: Returns the histograms that were computed over the input data. - """ - - input_data: numpy.ndarray[typing.Any, typing.Any] = data - return numpy.stack([ - numpy.stack([ - numpy.histogram( - sample.reshape(sample.shape[0], numpy.prod(sample.shape[1:3])), - bins=self.bins, - density=True - ) for sample in channel - ]) for channel in input_data.transpose(3, 0, 1, 2)]) - - class SSIM(Processor): """Represents a :py:class:`~corelay.processor.base.Processor`, which computes the structural similarity index (SSIM) of the data.""" diff --git a/docs/source/contributors-guide/contributing-code-or-documentation.rst b/docs/source/contributors-guide/contributing-code-or-documentation.rst index d3bd89c..6bbce57 100644 --- a/docs/source/contributors-guide/contributing-code-or-documentation.rst +++ b/docs/source/contributors-guide/contributing-code-or-documentation.rst @@ -27,7 +27,7 @@ Upon installing the supported Python versions, dependencies must be installed vi .. code-block:: console - $ uv --directory source sync --all-extras + $ uv --directory source sync To start your work, please create a new branch specifically for your feature or bug fix. When naming your branch, we recommend using kebab-case (lowercase words separated by hyphens) to clearly describe its purpose, e.g., ``my-new-feature``. diff --git a/docs/source/getting-started/installation.rst b/docs/source/getting-started/installation.rst index 96a86f6..2c2e642 100644 --- a/docs/source/getting-started/installation.rst +++ b/docs/source/getting-started/installation.rst @@ -29,4 +29,4 @@ If you'd like to try out the bleeding-edge development version or experiment wit $ git clone https://github.com/virelay/corelay.git $ cd corelay $ uv --directory source python install - $ uv --directory source sync --all-extras + $ uv --directory source sync diff --git a/source/corelay/base.py b/source/corelay/base.py index 563b420..611ae79 100644 --- a/source/corelay/base.py +++ b/source/corelay/base.py @@ -54,10 +54,11 @@ def __repr__(self) -> str: str: Returns a :py:class:`str` representation of the :py:class:`Param` instance. """ - # Sphinx AutoDoc uses __repr__ when it encounters the metadata of typing.Annotated; this is a reasonable thing to do, but then it tries to - # resolve the resulting string as types for cross-referencing, which is not possible with the default implementation of __repr__; to be able - # to get proper documentation, the fully-qualified name of the class is returned, because this enable Sphinx AutoDoc to reference the class in - # the documentation + # Sphinx AutoDoc uses repr when it encounters typing.Annotated, which in turn uses repr to get a string representation of its metadata; this + # is a reasonable thing to do, but then Intersphinx tries to resolve the resulting string as types for cross-referencing, which is not + # possible with the default implementation of __repr__; to be able to get proper documentation, the fully-qualified name of the class is + # returned, because this enable Sphinx AutoDoc to reference the class in the documentation; the tilde in front is interpreted by AutoDoc to + # mean that only the last part of the fully-qualified name should be displayed in the documentation return f'~{get_fully_qualified_name(self)}' @property diff --git a/source/corelay/io/storage.py b/source/corelay/io/storage.py index f18bf13..84c8f83 100644 --- a/source/corelay/io/storage.py +++ b/source/corelay/io/storage.py @@ -145,8 +145,8 @@ def write(self, data_out: typing.Any, data_in: typing.Any, meta: typing.Any) -> HashedHDF5._write_hdf5_recursively(data_out, group, 'data') except TypeError as exception: raise TypeError( - f'The data type of the output data "{type(data_out)}" is not supported. The must either be a NumPy array or a hierarchy of tuples ' - 'containing NumPy arrays and tuples of NumPy arrays.' + f'The data type of the output data "{type(data_out)}" is not supported. The data must either be a NumPy array or a hierarchy of ' + 'tuples containing NumPy arrays and tuples of NumPy arrays.' ) from exception group['meta'] = json.dumps(meta) @@ -161,9 +161,6 @@ def _read_hdf5_content_recursively(base: h5py.Group | h5py.Dataset) -> numpy.nda Args: base (h5py.Group | h5py.Dataset): The HDF5 group or dataset to read. - Raises: - TypeError: The data type of the input data is not supported. - Returns: numpy.ndarray[typing.Any, typing.Any] | RecursiveNumPyArrayTuple: Returns a tuple of :py:class:`~numpy.ndarray` or other tuples of :py:class:`~numpy.ndarray`, which themselves can be nested. Each tuple represents an HDF5 group. Nested tuples appear in the alphabetical @@ -174,10 +171,8 @@ def _read_hdf5_content_recursively(base: h5py.Group | h5py.Dataset) -> numpy.nda if isinstance(base, h5py.Group): return tuple(HashedHDF5._read_hdf5_content_recursively(base[key]) for key in sorted(base)) - if isinstance(base, h5py.Dataset): - dataset_data: numpy.ndarray[typing.Any, typing.Any] = base[()] - return dataset_data - raise TypeError('Unsupported output type!') + dataset_data: numpy.ndarray[typing.Any, typing.Any] = base[()] + return dataset_data @staticmethod def _write_hdf5_recursively(data: RecursiveNumPyArrayTuple | numpy.ndarray[typing.Any, typing.Any], group: h5py.Group, key: str) -> None: @@ -317,8 +312,7 @@ def __exit__(self, exception_type: type[Exception] | None, exception: Exception, was raised, otherwise it is :py:obj:`None`. """ - if self.io is not None: - self.io.close() + self.close() def __contains__(self, key: str) -> bool: """Check if the key exists in the storage. @@ -638,6 +632,9 @@ def __init__(self, path: str | pathlib.Path, mode: str = 'r', data_key: str | No :py:obj:`None`. **kwargs (typing.Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., :py:class:`DataStorageBase`. + + Raises: + ValueError: The mode is not "w", "r", or "a". """ # PyDocLint does not support the documentation of the constructor parameters both in the __init__ method and the class docstring, so we have @@ -647,6 +644,9 @@ def __init__(self, path: str | pathlib.Path, mode: str = 'r', data_key: str | No kwargs['data_key'] = data_key super().__init__(**kwargs) + if mode not in ['w', 'r', 'a']: + raise ValueError('Mode should be set to "w", "r", or "a".') + self.io: h5py.File = h5py.File(path, mode=mode) def read(self, data_in: typing.Any = None, meta: typing.Any = None) -> typing.Any: # pylint: disable=unused-argument @@ -730,7 +730,7 @@ def keys(self) -> collections.abc.KeysView[str]: return key_list @staticmethod - def _unpack(key: str | int, value: h5py.Dataset | h5py.Group | typing.Any) -> tuple[str | int, typing.Any]: + def _unpack(key: str | int, value: h5py.Dataset | h5py.Group) -> tuple[str | int, typing.Any]: """Unpacks the specified value. If the value is an HDF5 dataset, it is converted to a :py:class:`~numpy.ndarray` or a :py:class:`str`, depending on the data type of the dataset. If the value is a group, then it is recursively unpacked into an :py:class:`collections.OrderedDict`, which contains the keys and values of the group. The keys of the group are sorted to ensure that the @@ -740,10 +740,10 @@ def _unpack(key: str | int, value: h5py.Dataset | h5py.Group | typing.Any) -> tu Args: key (str | int): The key of the value to unpack. This can either be a :py:class:`str` or an :py:class:`int`. If the key is a :py:class:`str` and only contains numeric characters, it is converted to an :py:class:`int`. - value (h5py.Dataset | h5py.Group | typing.Any): The value that is to be unpacked. If the value is an HDF5 dataset, it is converted to a - NumPy array or a :py:class:`str`, depending on the data type of the dataset. If the value is a group, then it is recursively unpacked - into an :py:class:`collections.OrderedDict`, which contains the keys and values of the group. The keys of the group are sorted to - ensure that the order of the data is consistent. + value (h5py.Dataset | h5py.Group): The value that is to be unpacked. If the value is an HDF5 dataset, it is converted to a NumPy array or + a :py:class:`str`, depending on the data type of the dataset. If the value is a group, then it is recursively unpacked into an + :py:class:`collections.OrderedDict`, which contains the keys and values of the group. The keys of the group are sorted to ensure that + the order of the data is consistent. Returns: tuple[str | int, typing.Any]: Returns the unpacked key and value as a tuple. The key is either a :py:class:`str` or an :py:class:`int`. @@ -762,13 +762,11 @@ def _unpack(key: str | int, value: h5py.Dataset | h5py.Group | typing.Any) -> tu value = value[()] if string_info is not None: value = value.decode(string_info.encoding) + return key, value - # If the value is a group, it is converted to an ordered dictionary, which contains the keys and values of the group; since the numeric keys - # are converted to an integer, the dictionary can be accessed like a tuple or list - elif isinstance(value, h5py.Group): - value = collections.OrderedDict((HDF5Storage._unpack(k, v) for k, v in value.items())) - - # Returns the key and value as a tuple + # Since the value is not a dataset, it must be a group; groups are converted to an ordered dictionary, which contains the keys and values of + # the group; since the numeric keys are converted to an integer, the dictionary can be accessed like a tuple or list + value = collections.OrderedDict((HDF5Storage._unpack(k, v) for k, v in value.items())) return key, value @staticmethod diff --git a/source/corelay/pipeline/base.py b/source/corelay/pipeline/base.py index 343730e..1d42eec 100644 --- a/source/corelay/pipeline/base.py +++ b/source/corelay/pipeline/base.py @@ -34,10 +34,10 @@ def __init__( the inheritance hierarchy. """ - if default is not None: - default = ensure_processor(default, **kwargs) if obj is not None: obj = ensure_processor(obj) + if default is not None: + default = ensure_processor(default, **kwargs) super().__init__(slot, obj=obj, default=default, **kwargs) @@ -78,27 +78,25 @@ def obj(self, value: Processor | Callable[..., typing.Any] | None) -> None: if value is not None: value = ensure_processor(value) - self._obj = value - try: - self._consistent() + Plug.obj.fset(self, value) # type: ignore[attr-defined] except TypeError as exception: - raise TypeError('The processor is not consistent with the dtype.') from exception + raise TypeError(f'The data type of the processor of the "{type(self).__name__}" object is not of type "{self.dtype}".') from exception @obj.deleter def obj(self) -> None: """Deletes the :py:class:`~corelay.processor.base.Processor` contained in the :py:class:`TaskPlug` by setting it to :py:obj:`None`.""" - self.obj = None + Plug.obj.fset(self, None) # type: ignore[attr-defined] @property - def default(self) -> typing.Any: + def default(self) -> Processor | None: """Gets or sets the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug`. If the :py:attr:`~TaskPlug.default` :py:class:`~corelay.processor.base.Processor` is not set, then the :py:attr:`~corelay.plugboard.Plug.fallback` :py:class:`~corelay.processor.base.Processor` is retrieved instead. Returns: - typing.Any: Returns the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug`. If not set, + Processor | None: Returns the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug`. If not set, :py:attr:`~corelay.plugboard.Plug.fallback` is returned. """ @@ -109,16 +107,17 @@ def default(self) -> typing.Any: # have tested it, only in the getter, super().default can be used, as it is not, yet, overridden, but in the setter and deleter, the complete # function must be re-implemented; also, when the getter is overridden, not only the setter, but also the deleter must be overridden, as # otherwise, it will not be available anymore - return super().default + processor: Processor | None = super().default + return processor @default.setter - def default(self, value: typing.Any) -> None: + def default(self, value: Processor | Callable[..., typing.Any] | None) -> None: """Gets or sets the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug` and checks for consistency. It is ensured first, that the new default value is a :py:class:`~corelay.processor.base.Processor`. If not, it is converted to a :py:class:`~corelay.processor.base.Processor` using the py:func:`ensure_processor` function. Args: - value (typing.Any): The new default :py:class:`~corelay.processor.base.Processor` to set. + value (Processor | Callable[..., typing.Any] | None): The new default :py:class:`~corelay.processor.base.Processor` to set. Raises: TypeError: The default :py:class:`~corelay.processor.base.Processor` is not consistent with the :py:attr:`~corelay.plugboard.Plug.dtype`, @@ -128,18 +127,17 @@ def default(self, value: typing.Any) -> None: if value is not None: value = ensure_processor(value) - self._default = value - try: - self._consistent() + Plug.default.fset(self, value) # type: ignore[attr-defined] except TypeError as exception: - raise TypeError('The default processor is not consistent with the dtype.') from exception + raise TypeError( + f'The data type of the default processor of the "{type(self).__name__}" object is not of type "{self.dtype}".') from exception @default.deleter def default(self) -> None: """Deletes the default :py:class:`~corelay.processor.base.Processor` of the :py:class:`TaskPlug` by setting it to :py:obj:`None`.""" - self.default = None + Plug.default.fset(self, None) # type: ignore[attr-defined] class Task(Slot): @@ -150,7 +148,7 @@ class Task(Slot): def __init__( self, proc_type: type[Processor] = Processor, - default: Processor | Callable[..., typing.Any] = lambda data: data, + default: Processor | Callable[..., typing.Any] | None = lambda data: data, **kwargs: typing.Any ) -> None: """Initializes a new :py:class:`Task` instance. @@ -158,8 +156,9 @@ def __init__( Args: proc_type (type[Processor]): The type of :py:class:`~corelay.processor.base.Processor` allowed for this :py:class:`Task`. Defaults to :py:class:`~corelay.processor.base.Processor`. - default (Processor | Callable[..., typing.Any]): The default :py:class:`~corelay.processor.base.Processor` for the :py:class:`Task`, which - must either be a :py:class:`~corelay.processor.base.Processor` or a function. Defaults to the identity function. + default (Processor | Callable[..., typing.Any] | None): The default :py:class:`~corelay.processor.base.Processor` for the + :py:class:`Task`, which must either be a :py:class:`~corelay.processor.base.Processor` or a function. Defaults to the identity + function. **kwargs (typing.Any): Keyword arguments that are passed to the constructor of the class one step up in the class hierarchy, i.e., :py:class:`~corelay.plugboard.Slot`. @@ -181,10 +180,11 @@ def __repr__(self) -> str: str: Returns a :py:class:`str` representation of the :py:class:`Task` instance. """ - # Sphinx AutoDoc uses __repr__ when it encounters the metadata of typing.Annotated; this is a reasonable thing to do, but then it tries to - # resolve the resulting string as types for cross-referencing, which is not possible with the default implementation of __repr__; to be able - # to get proper documentation, the fully-qualified name of the class is returned, because this enable Sphinx AutoDoc to reference the class in - # the documentation + # Sphinx AutoDoc uses repr when it encounters typing.Annotated, which in turn uses repr to get a string representation of its metadata; this + # is a reasonable thing to do, but then Intersphinx tries to resolve the resulting string as types for cross-referencing, which is not + # possible with the default implementation of __repr__; to be able to get proper documentation, the fully-qualified name of the class is + # returned, because this enable Sphinx AutoDoc to reference the class in the documentation; the tilde in front is interpreted by AutoDoc to + # mean that only the last part of the fully-qualified name should be displayed in the documentation return f'~{get_fully_qualified_name(self)}' @property @@ -276,7 +276,8 @@ def checkpoint_processes(self) -> collections.OrderedDict[str, Processor]: checkpoint :py:class:`~corelay.processor.base.Processor` to the output :py:class:`~corelay.processor.base.Processor`. """ - checkpoint_processor_list = [] + processor: Processor + checkpoint_processor_list: list[tuple[str, Processor]] = [] for key, processor in reversed(self.collect_attr(Task).items()): checkpoint_processor_list.append((key, processor)) if processor.is_checkpoint: diff --git a/source/corelay/plugboard.py b/source/corelay/plugboard.py index 56bc323..04caaa5 100644 --- a/source/corelay/plugboard.py +++ b/source/corelay/plugboard.py @@ -4,7 +4,17 @@ """ import typing -from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, LambdaType, MethodType +from types import ( + BuiltinFunctionType, + BuiltinMethodType, + ClassMethodDescriptorType, + FunctionType, + LambdaType, + MethodDescriptorType, + MethodType, + MethodWrapperType, + WrapperDescriptorType +) import numpy @@ -81,10 +91,14 @@ def __init__( self._consistent() _function_types = ( - LambdaType, - MethodType, BuiltinFunctionType, BuiltinMethodType, + ClassMethodDescriptorType, + LambdaType, + MethodDescriptorType, + MethodType, + MethodWrapperType, + WrapperDescriptorType, numpy.ufunc, type(numpy.max) ) @@ -204,10 +218,11 @@ def __repr__(self) -> str: str: Returns a :py:class:`str` representation of the :py:class:`Slot` instance. """ - # Sphinx AutoDoc uses __repr__ when it encounters the metadata of typing.Annotated; this is a reasonable thing to do, but then it tries to - # resolve the resulting string as types for cross-referencing, which is not possible with the default implementation of __repr__; to be able - # to get proper documentation, the fully-qualified name of the class is returned, because this enable Sphinx AutoDoc to reference the class in - # the documentation + # Sphinx AutoDoc uses repr when it encounters typing.Annotated, which in turn uses repr to get a string representation of its metadata; this + # is a reasonable thing to do, but then Intersphinx tries to resolve the resulting string as types for cross-referencing, which is not + # possible with the default implementation of __repr__; to be able to get proper documentation, the fully-qualified name of the class is + # returned, because this enable Sphinx AutoDoc to reference the class in the documentation; the tilde in front is interpreted by AutoDoc to + # mean that only the last part of the fully-qualified name should be displayed in the documentation return f'~{get_fully_qualified_name(self)}' @property @@ -337,10 +352,14 @@ class will be :py:class:`EmptyInit`, which accepts no more keyword arguments and self._consistent() _function_types = ( - LambdaType, - MethodType, BuiltinFunctionType, BuiltinMethodType, + ClassMethodDescriptorType, + LambdaType, + MethodDescriptorType, + MethodType, + MethodWrapperType, + WrapperDescriptorType, numpy.ufunc, type(numpy.max) ) @@ -363,7 +382,8 @@ def _consistent(self) -> None: """ if self.obj is None: - raise TypeError(f'"{type(self.slot).__name__}" object "{self.slot.__name__}" is mandatory, yet it has been accessed without being set.') + raise TypeError( + f'"{type(self.slot).__name__}" object "{self.slot.__name__}" has been accessed without being set and no default value is available.') # If the user sets the dtype to FunctionType, then we should add some more types to check against, because many functions are not of type # FunctionType, e.g., expressions, methods, built-in functions, built-in methods, NumPy universal functions, and NumPy array functions @@ -552,11 +572,13 @@ def _get_plug(self, name: str, default: typing.Any = None) -> Plug: return slot.get_plug(self._instance, default=default) def __get__(self, instance: typing.Any, owner: typing.Any) -> 'SlotDefaultAccess': # pylint: disable=unused-argument - """Gets a new instance of :py:class:`SlotDefaultAccess`, initialized with the provided instance value. + """Is invoked when the property, the :py:class:`~corelay.plugboard.SlotDefaultAccess` is stored in, is retrieved. Returns a new + :py:class:`~corelay.plugboard.SlotDefaultAccess` instance initialized with the provided instance value, which is the instance of the class + that is the owner of the :py:class:`SlotDefaultAccess`. Args: instance (typing.Any): The instance of the class the :py:class:`SlotDefaultAccess` is associated with. - owner (typing.Any): The owner class of the :py:class:`SlotDefaultAccess`. + owner (typing.Any): The class of the ``instance`` that owns the :py:class:`SlotDefaultAccess`. Returns: SlotDefaultAccess: Returns a new instance of :py:class:`SlotDefaultAccess` initialized with the provided instance value. @@ -565,8 +587,8 @@ def __get__(self, instance: typing.Any, owner: typing.Any) -> 'SlotDefaultAccess return type(self)(instance) def __set__(self, instance: typing.Any, value: dict[str, typing.Any]) -> None: - """Sets the default values of the associated owner class instance's slots by assigning a the values of the :py:class:`dict` specified in - ``value``. + """Is invoked when the property, the :py:class:`~corelay.plugboard.SlotDefaultAccess` is stored in, is set. Sets the default values of the + associated owner class instance's slots by assigning a the values of the :py:class:`dict` specified in ``value``. Args: instance (typing.Any): The instance of the class the :py:class:`SlotDefaultAccess` is associated with. @@ -619,11 +641,6 @@ def __setattr__(self, name: str, value: typing.Any) -> None: except AttributeError as exception: raise AttributeError(f'"{type(self._instance)}" object has no attribute "{name}" of type "{Slot}".') from exception except TypeError as exception: - slot: Slot | None = getattr(type(self._instance), name, None) - if slot is not None: - raise TypeError( - f'The data type of the default value of the "{type(self._instance)}" object is not of type "{slot.dtype}".' - ) from exception raise TypeError(f'The data type of the default value of the "{type(self._instance)}" object is invalid.') from exception def __delattr__(self, name: str) -> None: diff --git a/source/corelay/processor/affinity.py b/source/corelay/processor/affinity.py index ab0d068..c8fd3d3 100644 --- a/source/corelay/processor/affinity.py +++ b/source/corelay/processor/affinity.py @@ -109,5 +109,5 @@ def function(self, data: typing.Any) -> typing.Any: typing.Any: Returns a NumPy array :py:class:`~numpy.ndarray` containing the RBF affinity matrix. """ - affinity = numpy.exp(-data / (2 * self.sigma ** 2)) + affinity = numpy.exp(-data**2 / (2 * self.sigma**2)) return affinity diff --git a/source/corelay/processor/flow.py b/source/corelay/processor/flow.py index dbebf1b..7159508 100644 --- a/source/corelay/processor/flow.py +++ b/source/corelay/processor/flow.py @@ -3,7 +3,7 @@ """ import typing -from collections.abc import Iterable, Iterator, Sequence +from collections.abc import Iterable, Iterator, Mapping, Sequence from typing import Annotated from corelay.base import Param @@ -50,11 +50,14 @@ def function(self, data: typing.Any) -> typing.Any: as the indices. """ - def extract_elements_from_indices(iterable: Sequence[typing.Any], indices: tuple[int | tuple[int], ...]) -> tuple[typing.Any, ...]: + def extract_elements_from_indices( + iterable: Sequence[typing.Any] | Mapping[typing.Any, typing.Any], + indices: tuple[int | tuple[int], ...] + ) -> tuple[typing.Any, ...]: """Recursively extracts the elements of the specified ``iterable`` based on the specified ``indices``. Args: - iterable (Sequence[typing.Any]): The iterable from which to extract the elements. + iterable (Sequence[typing.Any] | Mapping[typing.Any, typing.Any]): The iterable from which to extract the elements. indices (tuple[int | tuple[int], ...]): The indices to extract. This can be a tuple of integers or other tuples of integers. Raises: @@ -66,17 +69,19 @@ def extract_elements_from_indices(iterable: Sequence[typing.Any], indices: tuple results = [] for index in indices: - if isinstance(index, Iterable): + if isinstance(index, Sequence) and not isinstance(index, str): extracted_element = extract_elements_from_indices(iterable, index) else: try: extracted_element = iterable[index] - except KeyError as err: - raise TypeError(f'The "{index}" is not a valid index for "{iterable}".') from err + except KeyError as exception: + raise TypeError(f'The "{index}" is not a valid index for "{iterable}".') from exception + except IndexError as exception: + raise TypeError(f'The "{index}" is out of bounds for "{iterable}".') from exception results.append(extracted_element) return tuple(results) - if not isinstance(data, Sequence): + if not isinstance(data, (Sequence, Mapping)): data = (data,) try: return extract_elements_from_indices(data, self.indices) diff --git a/source/corelay/processor/laplacian.py b/source/corelay/processor/laplacian.py index 25a8772..25ac95b 100644 --- a/source/corelay/processor/laplacian.py +++ b/source/corelay/processor/laplacian.py @@ -87,5 +87,5 @@ def function(self, data: typing.Any) -> typing.Any: typing.Any: Returns the random walk normal graph Laplacian, which is a sparse representation of the random walk graph Laplacian matrix. """ - degree = scipy.sparse.diags(a1ifmat(data.sum(1))**-1., 0) + degree = scipy.sparse.diags(a1ifmat(data.sum(axis=1))**-1.0, 0) return degree @ data diff --git a/source/corelay/processor/preprocessing.py b/source/corelay/processor/preprocessing.py index 445132e..3644eee 100644 --- a/source/corelay/processor/preprocessing.py +++ b/source/corelay/processor/preprocessing.py @@ -45,6 +45,9 @@ class Histogram(PreProcessor): bins (int): The number of bins for the histogram. Defaults to 32. """ + channels_first: Annotated[bool, Param(bool, True)] + """A value indicating whether the input data is in channels-first format or not. Defaults to :py:obj:`True`.""" + bins: Annotated[int, Param(int, 32)] """Number of bins for the histogram. Defaults to 32.""" @@ -52,28 +55,38 @@ def function(self, data: typing.Any) -> typing.Any: """Computes channel-wise histograms from the input data. Args: - data (typing.Any): The input data to compute histograms for, which is should be an image as a NumPy array of shape - `(number_of_samples, number_of_channels, height, width)`. + data (typing.Any): The input data to compute histograms for, which is should be a NumPy array of images. The images can be in one of the + following formats: + + 1. `(number_of_samples, number_of_channels, height, width)`, if :py:attr:`~Histogram.channels_first` is set to :py:obj:`True`. + 2. `(number_of_samples, height, width, number_of_channels)`, if :py:attr:`~Histogram.channels_first` is set to :py:obj:`False`. + 3. `(number_of_samples, height, width)`. Returns: - typing.Any: Returns a NumPy array, which contains the channel-wise histograms of the input data of shape - `(number_of_samples, number_of_channels, bins)`. + typing.Any: Returns a NumPy array, which contains the channel-wise histograms of the input data as a NumPy array be of shape + `(number_of_samples, number_of_channels, bins)`. If the input data is grayscale, then the number of channels will be 1. """ input_data: numpy.ndarray[typing.Any, typing.Any] = data - number_of_samples, number_of_channels, height, width = input_data.shape - - channel_minima: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = input_data.min((0, 2, 3)) - channel_maxima: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] = input_data.max((0, 2, 3)) - channel_range = list(zip(channel_minima, channel_maxima)) - histogram, _ = numpy.histogramdd( - input_data.reshape(number_of_samples * number_of_channels, height * width), - bins=self.bins, - range=channel_range, - density=True + is_grayscale = len(input_data.shape) == 3 + if is_grayscale: + input_data = input_data[:, numpy.newaxis, :, :] + + if not self.channels_first: + input_data = numpy.moveaxis(input_data, -1, 1) + + return ( + numpy.stack([ + numpy.stack([ + numpy.histogram( + channel.reshape(numpy.prod(channel.shape)), + bins=self.bins, + density=True + )[0] for channel in sample + ]) for sample in input_data + ]) ) - return histogram.reshape(number_of_samples, number_of_channels, self.bins) class ImagePreProcessor(PreProcessor): @@ -140,9 +153,10 @@ def function(self, data: typing.Any) -> typing.Any: data (typing.Any): The input data, which contains the images that are to be resized. The input data should be a NumPy array in one of the following formats: - 1. `(batch_size, number_of_channels, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`True`. - 2. `(batch_size, height, width, number_of_channels)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. - 3. `(batch_size, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. + 1. `(number_of_samples, number_of_channels, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`True`. + 2. `(number_of_samples, height, width, number_of_channels)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to + :py:obj:`False`. + 3. `(number_of_samples, height, width)`. Returns: typing.Any: Returns a NumPy array containing the resized images, with a shape that matches the input data format. @@ -150,7 +164,8 @@ def function(self, data: typing.Any) -> typing.Any: input_images: numpy.ndarray[typing.Any, typing.Any] = data - if self.channels_first: + is_grayscale = len(input_images.shape) == 3 + if not is_grayscale and self.channels_first: input_images = numpy.moveaxis(data, 1, -1) resized_images = numpy.stack([ @@ -162,7 +177,7 @@ def function(self, data: typing.Any) -> typing.Any: ) for image in input_images ]) - if self.channels_first: + if not is_grayscale and self.channels_first: resized_images = numpy.moveaxis(resized_images, -1, 1) return resized_images @@ -194,9 +209,10 @@ def function(self, data: typing.Any) -> typing.Any: data (typing.Any): The input data, which contains the images that are to be rescaled. The input data should be a NumPy array in one of the following formats: - 1. `(batch_size, number_of_channels, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`True`. - 2. `(batch_size, height, width, number_of_channels)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. - 3. `(batch_size, height, width) `, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`False`. + 1. `(number_of_samples, number_of_channels, height, width)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to :py:obj:`True`. + 2. `(number_of_samples, height, width, number_of_channels)`, if :py:attr:`~ImagePreProcessor.channels_first` is set to + :py:obj:`False`. + 3. `(number_of_samples, height, width)`. Returns: typing.Any: Returns a NumPy array containing the rescaled images, with a shape that matches the input data format. @@ -205,7 +221,8 @@ def function(self, data: typing.Any) -> typing.Any: input_data: numpy.ndarray[typing.Any, typing.Any] = data images_are_multi_channel = len(input_data.shape) > 3 - if self.channels_first: + is_grayscale = len(input_data.shape) == 3 + if not is_grayscale and self.channels_first: input_data = numpy.moveaxis(input_data, 1, -1) rescaled_images = numpy.stack([ @@ -218,7 +235,7 @@ def function(self, data: typing.Any) -> typing.Any: ) for image in input_data ]) - if self.channels_first: + if not is_grayscale and self.channels_first: rescaled_images = numpy.moveaxis(rescaled_images, -1, 1) return rescaled_images @@ -237,13 +254,13 @@ class Pooling(PreProcessor): kwargs (dict): Additional keyword arguments to pass to the image pre-processing function. Defaults to an empty :py:class:`dict`. filter (int): The order of interpolation. The order has to be in the range 0-5. Defaults to 1 (bi-linear). channels_first (bool): A value indicating whether the input data is in channels-first format or not. Defaults to :py:obj:`True`. - stride (tuple[int]): The pooling stride, which should be of shape `(batch_size, number_of_channels, height, width)`. Defaults to + stride (tuple[int]): The pooling stride, which should be of shape `(number_of_samples, number_of_channels, height, width)`. Defaults to `(1, 1, 2, 2)`. pooling_function (FunctionType): The pooling function to use to reduce the selected blocks. Defaults to :py:func:`numpy.sum`. """ stride: Annotated[tuple[int], Param(tuple, (1, 1, 2, 2))] - """The pooling stride, which should be of shape `(batch_size, number_of_channels, height, width)`. Defaults to `(1, 1, 2, 2)`.""" + """The pooling stride, which should be of shape `(number_of_samples, number_of_channels, height, width)`. Defaults to `(1, 1, 2, 2)`.""" pooling_function: Annotated[FunctionType, Param(FunctionType, numpy.sum)] """The pooling function to use to reduce the selected blocks. Defaults to :py:func:`numpy.sum`.""" diff --git a/source/corelay/tracker.py b/source/corelay/tracker.py index dda97b9..6550b67 100644 --- a/source/corelay/tracker.py +++ b/source/corelay/tracker.py @@ -128,10 +128,9 @@ def is_slot(object_to_check: typing.Any) -> bool: return True classes_checked.append(current_class) - if hasattr(current_class, '__bases__'): - for base_class in current_class.__bases__: - if base_class not in classes_checked and base_class not in classes_to_check: - classes_to_check.append(base_class) + for base_class in current_class.__bases__: + if base_class not in classes_checked and base_class not in classes_to_check: + classes_to_check.append(base_class) return False @@ -143,7 +142,8 @@ def is_slot(object_to_check: typing.Any) -> bool: 'deprecated. This syntax is currently still supported, but it will be removed in a future version of CoRelAy. Please refer to ' 'the migration guide to find out why this syntax was deprecated and how to update your code: ' 'https://corelay.readthedocs.io/en/latest/migration-guide/migrating-from-v0.2-to-v0.3.html.', - DeprecationWarning + DeprecationWarning, + stacklevel=2 ) # Retrieves the declared class attributes, which were declared using Annotated and are are not special "dunder" attributes, like __class__, @@ -155,8 +155,6 @@ def is_slot(object_to_check: typing.Any) -> bool: continue if not hasattr(attribute_value, '__metadata__'): continue - if not isinstance(attribute_value.__metadata__, tuple) or len(attribute_value.__metadata__) == 0: - continue tracked_declared_class_attributes[attribute_name] = attribute_value.__metadata__[0] # The way that slots work is, that they override the __get__, __set__, and __delete__ methods of the class, which are invoked when the class diff --git a/source/corelay/utils.py b/source/corelay/utils.py index a6cf6e9..0093caa 100644 --- a/source/corelay/utils.py +++ b/source/corelay/utils.py @@ -11,7 +11,18 @@ import typing from collections.abc import Callable, Iterable, Iterator from importlib import import_module -from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, LambdaType, MethodType, ModuleType +from types import ( + BuiltinFunctionType, + BuiltinMethodType, + ClassMethodDescriptorType, + FunctionType, + LambdaType, + MethodDescriptorType, + MethodType, + MethodWrapperType, + ModuleType, + WrapperDescriptorType +) from typing import NoReturn, overload import numpy @@ -52,26 +63,24 @@ def zip_equal(*args: Iterable[typing.Any]) -> Iterator[tuple[typing.Any, ...]]: yield tuple(zipped_element) -def get_lambda_expression_source_code(lambda_expression: LambdaType) -> str: +def get_lambda_expression_source_code(lambda_expression: Callable[..., typing.Any]) -> str: """Returns the source code of the specified lambda expression if possible. This is done by retrieving the source code of the lambda expression and parsing it into an abstract syntax tree (AST). The AST node that represents the lambda expression is then used to retrieve the source code of the - lambda expression by repeatedly removing the trailing junk from the source code until the source code can be compiled without errors, is at least - as long as the shortest possible lambda expression, and the length of the compiled byte code is the same as the length as the byte code of the - original lambda expression. + lambda expression. Note: This function was adapted from the original implementation by Karol Kuczmarski, outline in their blog post `Source code of a Python lambda `_. The code was published as a `Gist on GitHub `_ and was published under the `Creative Commons Attribution-ShareAlike 4.0 International License `_. The original code - was modified to fit the coding style of this project and to return "" instead of :py:obj:`None` if the source - code cannot be retrieved. + was modified to fit the coding style of this project and to return the representation of the passed object instead of :py:obj:`None` if the + source code cannot be retrieved. Args: - lambda_expression (LambdaType): The lambda expression for which the source code is to be returned. + lambda_expression (Callable[..., typing.Any]): The lambda expression for which the source code is to be returned. Returns: - str: Returns the source code of the lambda expression if possible, otherwise "" is returned. + str: Returns the source code of the lambda expression if possible, otherwise the representation of the object is returned. """ # Retrieves the line of the source code that contains the lambda expression, if the lambda expression is defined over multiple lines, then they @@ -79,53 +88,73 @@ def get_lambda_expression_source_code(lambda_expression: LambdaType) -> str: try: source_lines, _ = inspect.getsourcelines(lambda_expression) except (IOError, TypeError): - return '' - if len(source_lines) != 1: - return '' + return repr(lambda_expression) source_code = os.linesep.join(source_lines).strip() - # Parses the source code into an AST and retrieves the AST node that represents the lambda expression + # Parses the source code into an AST and retrieves the AST nodes that represent lambda expressions; if none were found, then "" is + # returned; if only a single lambda expression was found, then its code segment is returned source_ast = ast.parse(source_code) - lambda_ast_node = next((node for node in ast.walk(source_ast) if isinstance(node, ast.Lambda)), None) - if lambda_ast_node is None: - return '' - - # Since we can (and most likely will) get source lines where the lambda expression is just a part of bigger expressions, they will have some - # trailing junk after their definition; unfortunately, AST nodes only keep their _starting_ offsets from the original source, so we have to - # determine the end ourselves; this is done by gradually shaving extra junk from after the definition while ensuring that the code is still valid, - # is at least as long as the shortest possible lambda expression, and that the length of the compiled byte code is the same as the length of the - # byte code of the original lambda expression - lambda_source_code = source_code[lambda_ast_node.col_offset:] - lambda_body_source_code = source_code[lambda_ast_node.body.col_offset:] - length_of_smallest_valid_lambda_expression = len('lambda:_') - while len(lambda_source_code) > length_of_smallest_valid_lambda_expression: + lambda_expression_source_segments: list[str] = [ + source_segment for source_segment in { + ast.get_source_segment(source_code, node) for node in ast.walk(source_ast) if isinstance(node, ast.Lambda) + } if source_segment is not None + ] + if not lambda_expression_source_segments: + return repr(lambda_expression) + if len(lambda_expression_source_segments) == 1: + return lambda_expression_source_segments[0] + + # If there are multiple lambda expressions in the source code, then we need to compile the lambda functions to further compare them; if none of + # them could be compiled, then "" is returned; if only a single one could be compiled, then its source code is returned + compiled_lambda_expressions: dict[str, LambdaType] = {} + for source_segment in lambda_expression_source_segments: try: - - # What's annoying is that sometimes the junk even parses, but results in a different lambda; You'd probably have to be deliberately - # malicious to exploit it but here's one way: - # - # lambda_expression_tuple = lambda x: False, lambda x: True - # get_lambda_expression_source_code(lambda_expression_tuple[0]) - # - # Ideally, we'd just keep shaving until we get the same code, but that most likely won't happen because we can't replicate the exact - # closure environment; thus the next best thing is to assume some divergence due to, e.g., LOAD_GLOBAL in original code being LOAD_FAST in - # the one compiled above, or vice versa; But the resulting code should at least be the same length, if otherwise the same operations are - # performed in it - code = compile(lambda_body_source_code, '', 'eval') - if len(code.co_code) == len(lambda_expression.__code__.co_code): - return lambda_source_code - - # Syntax errors are expected, so we just ignore them; they are most likely caused by the trailing junk in the source code that has not, yet, - # been removed - except SyntaxError: - pass - - # Shaves off the last character of the source code of the lambda expression and the body of the lambda expression - lambda_source_code = lambda_source_code[:-1] - lambda_body_source_code = lambda_body_source_code[:-1] - - # If the source code cannot be retrieved, then "" is returned - return '' + compiled_lambda_expressions[source_segment] = eval(compile(source_segment, '', 'eval')) # pylint: disable=eval-used + except SyntaxError: # pragma: no cover + continue + if not compiled_lambda_expressions: # pragma: no cover + return repr(lambda_expression) + if len(compiled_lambda_expressions) == 1: # pragma: no cover + return next(iter(compiled_lambda_expressions.keys())) + + # First we check if which of the lambda expressions that were compiled match the parameters of the specified lambda expression; if none of them + # match, then we return ""; if only one matches, then we return its source code + lambda_expressions_with_matching_parameters: dict[str, LambdaType] = {} + for lambda_expression_source_segment, compiled_lambda_expression in compiled_lambda_expressions.items(): + if compiled_lambda_expression.__code__.co_varnames == lambda_expression.__code__.co_varnames: + lambda_expressions_with_matching_parameters[lambda_expression_source_segment] = compiled_lambda_expression + if not lambda_expressions_with_matching_parameters: # pragma: no cover + return repr(lambda_expression) + if len(lambda_expressions_with_matching_parameters) == 1: + return next(iter(lambda_expressions_with_matching_parameters.keys())) + + # If there are multiple lambda expressions with matching parameters, then we need to compare their byte code; since we cannot replicate the exact + # closure environment, there may be some divergence in the byte codes, but if they do not use any global variables, then the byte codes should be + # identical and we can use the byte code to determine which lambda expression is the correct one + for lambda_expression_source_segment, compiled_lambda_expression in lambda_expressions_with_matching_parameters.items(): + if compiled_lambda_expression.__code__.co_code == lambda_expression.__code__.co_code: + return lambda_expression_source_segment + + # Finally, if none of the lambda expressions are an exact byte code match, we can compare their byte code lengths; maybe, the lambda expressions + # differ in their complexity and the byte code of the correct lambda expression only differs in some minor way, for example, the LOAD_GLOBAL + # instruction was used in the original code, but was compiled as a LOAD_FAST instruction, due to the closure environment being different; in such + # a case, the byte code lengths should be similar, so we can use the difference in the byte code lengths to determine which lambda expression is + # the correct one; since it is unlikely that the candidate lambda expressions will be smaller than the actual lambda expression, we can directly + # dismiss candidates whose difference in byte code length is negative; in case the byte code lengths between the candidate lambda expressions does + # not differ, i.e., there are multiple lambda expressions with the same byte code length, then we cannot determine which one is the correct one, + # so we return the object representation instead + lambda_expression_byte_code_length_differences: dict[str, int] = {} + for lambda_expression_source_segment, compiled_lambda_expression in lambda_expressions_with_matching_parameters.items(): + byte_code_length_difference = len(compiled_lambda_expression.__code__.co_code) - len(lambda_expression.__code__.co_code) + if byte_code_length_difference >= 0: + lambda_expression_byte_code_length_differences[lambda_expression_source_segment] = byte_code_length_difference + sorted_byte_code_length_differences = list(sorted(lambda_expression_byte_code_length_differences.values())) + if len(sorted_byte_code_length_differences) > 1 and sorted_byte_code_length_differences[0] == sorted_byte_code_length_differences[1]: + return repr(lambda_expression) + return min( + lambda_expression_byte_code_length_differences, + key=lambda source_segment: lambda_expression_byte_code_length_differences[source_segment] + ) def get_object_representation(obj: typing.Any) -> str: @@ -140,10 +169,24 @@ def get_object_representation(obj: typing.Any) -> str: str: Returns a :py:class:`str` representation of the object. """ - if isinstance(obj, LambdaType): + if isinstance(obj, LambdaType) and '' in str(obj): return get_lambda_expression_source_code(obj) - if isinstance(obj, (type, ModuleType, FunctionType, MethodType, BuiltinFunctionType, BuiltinMethodType, numpy.ufunc, type(numpy.max))): + if isinstance(obj, ( + type, + BuiltinFunctionType, + BuiltinMethodType, + ClassMethodDescriptorType, + FunctionType, + LambdaType, + MethodDescriptorType, + MethodType, + MethodWrapperType, + ModuleType, + WrapperDescriptorType, + numpy.ufunc, + type(numpy.max)) + ): return get_fully_qualified_name(obj) return repr(obj) @@ -153,17 +196,36 @@ def get_fully_qualified_name(obj: typing.Any) -> str: """Returns the fully qualified name of the object, which is the module name and the name of the object. Args: - obj (typing.Any): The object for which the fully qualified name is to be returned. + obj (typing.Any): The object for which the fully qualified name is to be returned. If the object is a :py:class:`type`, then the + fully-qualified name of the type is returned, otherwise the fully-qualified name of the class of the object is returned. Returns: str: Returns the fully qualified name of the object. """ - object_class: type = obj.__class__ - object_module = object_class.__module__ - if object_module == 'builtins': - return object_class.__qualname__ - return object_module + '.' + object_class.__qualname__ + if obj is None: + return 'None' + + if isinstance(obj, (BuiltinFunctionType, BuiltinMethodType, FunctionType, LambdaType, numpy.ufunc, type(numpy.max))): + if obj.__module__ is None or obj.__module__ in ('builtins', '__main__'): + return obj.__qualname__ + return f'{obj.__module__}.{obj.__qualname__}' + + if isinstance(obj, MethodType): + if obj.__self__.__class__.__module__ == '__main__': + return obj.__func__.__qualname__ + return f'{obj.__self__.__class__.__module__}.{obj.__qualname__}' + + if isinstance(obj, (ClassMethodDescriptorType, MethodDescriptorType, MethodWrapperType, WrapperDescriptorType)): + return obj.__qualname__ + + if isinstance(obj, ModuleType): + return obj.__name__ + + object_type = obj if isinstance(obj, type) else type(obj) + if object_type.__module__ in ('builtins', '__main__'): + return object_type.__qualname__ + return f'{object_type.__module__}.{object_type.__qualname__}' def dummy_from_module_import(module_name: str) -> Callable[..., typing.Any]: @@ -267,7 +329,7 @@ def import_or_stub(module_name: str) -> types.ModuleType: @overload -def import_or_stub(module_name: str, type_and_function_names: str) -> Callable[..., typing.Any]: +def import_or_stub(module_name: str, type_and_function_names: str) -> type[typing.Any] | Callable[..., typing.Any]: """Tries to import a type or a function from a module. If the import fails, the requested type or function is replaced with a dummy that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved type or function will raise an exception that tells the user how to install the missing dependencies for the functionality to work. @@ -277,13 +339,13 @@ def import_or_stub(module_name: str, type_and_function_names: str) -> Callable[. type_and_function_names (str): The name of the type or function that is to be imported from the module. Returns: - Callable[..., typing.Any]: Returns the imported type or function. If the import of the module fails, a dummy is returned that will raise an - exception when used, telling the user how to install the missing dependencies. + type[typing.Any] | Callable[..., typing.Any]: Returns the imported type or function. If the import of the module fails, a dummy is returned + that will raise an exception when used, telling the user how to install the missing dependencies. """ @overload -def import_or_stub(module_name: str, type_and_function_names: tuple[str, ...]) -> list[types.ModuleType | Callable[..., typing.Any]]: +def import_or_stub(module_name: str, type_and_function_names: tuple[str, ...]) -> list[type | Callable[..., typing.Any]]: """Tries to import types and/or functions from a module. If the import fails, the requested types and/or functions are replaced with dummies that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved types and/or functions will raise an exception that tells the user how to install the missing dependencies for the functionality to work. @@ -293,15 +355,15 @@ def import_or_stub(module_name: str, type_and_function_names: tuple[str, ...]) - type_and_function_names (tuple[str, ...]): The names of the types and/or functions that are to be imported from the module. Returns: - list[types.ModuleType | Callable[..., typing.Any]]: Returns a list of the imported modules, types and/or functions. If the import of - the module fails, dummies are returned that will raise an exception when used, telling the user how to install the missing dependencies. + list[type | Callable[..., typing.Any]]: Returns a list of the imported modules, types and/or functions. If the import of the module fails, + dummies are returned that will raise an exception when used, telling the user how to install the missing dependencies. """ def import_or_stub( module_name: str, type_and_function_names: str | tuple[str, ...] | None = None -) -> list[types.ModuleType | Callable[..., typing.Any]] | types.ModuleType | Callable[..., typing.Any]: +) -> types.ModuleType | type[typing.Any] | Callable[..., typing.Any] | list[type | Callable[..., typing.Any]]: """Tries to import a module, or types and/or functions from a module. If the import fails, the requested module, or types and/or functions are replaced with dummies that will raise an exception when used. This is useful for optional dependencies of a package, because the retrieved module, or types and/or functions will raise an exception that tells the user how to install the missing dependencies for the functionality to work. @@ -319,22 +381,20 @@ def import_or_stub( installing a missing dependency. Returns: - list[types.ModuleType | Callable[..., typing.Any]] | types.ModuleType | Callable[..., typing.Any]: Returns the imported - module, or types and/or functions that were imported from the module. If the import of the module fails, dummies for the module, or types - and/or functions are returned that will raise an exception when used, telling the user how to install the missing dependencies. + types.ModuleType | type[typing.Any] | Callable[..., typing.Any] | list[type | Callable[..., typing.Any]]: Returns the imported module, or + types and/or functions that were imported from the module. If the import of the module fails, dummies for the module, or types and/or + functions are returned that will raise an exception when used, telling the user how to install the missing dependencies. """ - # Creates a list of the modules, types, and functions that were imported - modules_types_and_functions: list[types.ModuleType | type | Callable[..., typing.Any]] = [] - - # If a list of types and functions to import was provided, then they are imported (or replaced by dummies) and added to the list + # If a list of types and functions to import was provided, then they are imported from the specified module(or replaced by dummies) if type_and_function_names is not None: type_and_function_names = (type_and_function_names, ) if isinstance(type_and_function_names, str) else type_and_function_names + types_and_functions: list[type | Callable[..., typing.Any]] = [] try: imported_module = import_module(module_name) except ImportError: - modules_types_and_functions = [dummy_from_module_import(module_name) for _ in type_and_function_names] + types_and_functions = [dummy_from_module_import(module_name) for _ in type_and_function_names] else: for type_or_function_name in type_and_function_names: try: @@ -344,18 +404,15 @@ def import_or_stub( f'Cannot import name "{type_or_function_name}" from "{module_name}" ({imported_module.__file__}).' ) from exception - modules_types_and_functions.append(type_or_function) + types_and_functions.append(type_or_function) - # If, however, no list of types and functions to import was provided, then the entire module is imported (or replaced by a stub) and added to the - # list - else: - try: - modules_types_and_functions = [import_module(module_name)] - except ImportError: - modules_types_and_functions = [dummy_import_module(module_name)] + if len(types_and_functions) == 1: + return types_and_functions[0] + return types_and_functions - # If only a single module/type/function was imported, then it is returned directly, otherwise a list of the imported modules/types/functions is - # returned - if len(modules_types_and_functions) == 1: - return modules_types_and_functions[0] - return modules_types_and_functions + # If no list of types and functions to import was provided, then the entire module is imported (or replaced by a stub) and returned + try: + return import_module(module_name) + except ImportError: + module: ModuleType = dummy_import_module(module_name) + return module diff --git a/source/pyproject.toml b/source/pyproject.toml index 2754ea9..86b8775 100644 --- a/source/pyproject.toml +++ b/source/pyproject.toml @@ -45,12 +45,12 @@ authors = [ # The project's dependencies, which are included in the requirements of the published package dependencies = [ "h5py>=3.13.0,<4.0.0", - "matplotlib>=3.10.1,<4.0.0", + "matplotlib>=3.10.3,<4.0.0", "metrohash-python>=1.1.3.3,<2.0.0", - "numpy>=2.2.5,<3.0.0", + "numpy>=2.2.6,<3.0.0", "scikit-image>=0.25.2,<1.0.0", "scikit-learn>=1.6.1,<2.0.0", - "scipy>=1.15.2,<2.0.0" + "scipy>=1.15.3,<2.0.0" ] # The PyPI classifiers that describe the project @@ -78,9 +78,11 @@ Changelog = "https://github.com/virelay/corelay/blob/main/CHANGELOG.md" # them provides additional functionality, but is not required to use the package [project.optional-dependencies] -# Dependencies required for using the Uniform Manifold Approximation and Projection (UMAP) dimensionality reduction algorithm +# Dependencies required for using the Uniform Manifold Approximation and Projection (UMAP) dimensionality reduction algorithm (the UMAP-Learn package +# version 0.5.8 has already been released, but it is not yet available on PyPI; therefore, the package is installed directly from the Git repository; +# when version 0.5.8 becomes available on PyPI, this dependency can be replaced with "umap-learn>=0.5.8,<0.6.0") umap = [ - "umap-learn>=0.5.7,<1.0.0" + "umap-learn@git+https://github.com/lmcinnes/umap@release-0.5.8" ] # Dependencies required for using the Hierarchical Density-Based Spatial Clustering of Applications with Noise (HDBSCAN) clustering algorithm @@ -99,30 +101,43 @@ dev = [ { include-group = "docs" } ] +# Extra dependencies of the project, which may be installed optionally when installing the package; these are needed for testing and linting (for the +# reasoning of installing the UMAP-Learn package directly from the Git repository, see the comment above in the "umap" optional dependency) +optional-dependencies = [ + "hdbscan>=0.8.40,<1.0.0", + "umap-learn@git+https://github.com/lmcinnes/umap@release-0.5.8" +] + # Dependencies required for testing testing = [ + "coverage>=7.8.2,<8.0.0", + "pytest-cov>=6.1.1,<7.0.0", + "pytest>=8.4.0,<9.0.0", + "tox-uv>=1.26.0,<2.0.0", "tox>=4.26.0,<5.0.0", - "tox-uv>=1.25.0,<2.0.0", - "pytest>=8.3.5,<9.0.0", - "coverage>=7.8.0,<8.0.0", - "pytest-cov>=6.1.1,<7.0.0" + { include-group = "optional-dependencies" } ] # Dependencies required for linting linting = [ - "pylint>=3.3.7,<4.0.0", + "mypy>=1.16.0,<2.0.0", "pycodestyle>=2.13.0,<3.0.0", "pydoclint>=0.6.6,<1.0.0", - "mypy>=1.15.0,<2.0.0", + "pylint>=3.3.7,<4.0.0", + "pytest-mypy>=1.0.1,<2.0.0", "scipy-stubs>=1.15.3.0,<2.0.0", - "pytest-mypy>=1.0.1,<2.0.0" + { include-group = "optional-dependencies" } ] -# Dependencies required for building the documentation +# Dependencies required for building the documentation (Pybtex is a dependency of the BibTeX extension for Sphinx, which is used to generate the +# bibliography in the documentation; the version of Pybtex available on PyPI is 0.24.0, which is still using the deprecated "pkg_resources" module for +# loading plugins; the problem has already been fixed in the development version, but it is not yet released; therefore, the development version is +# installed directly from the Git repository; if and when a successor to 0.24.0 is released, this dependency can be removed) docs = [ - "sphinx>=8.2.3,<9.0.0", + "pybtex@git+https://bitbucket.org/pybtex-devs/pybtex@9b97822f5517fc7893456b9827589a003ea7076a", "sphinx-copybutton>=0.5.2,<1.0.0", "sphinx-rtd-theme>=3.0.2,<4.0.0", + "sphinx>=8.2.3,<9.0.0", "sphinxcontrib.bibtex>=2.6.3,<3.0.0", "sphinxcontrib.datatemplates>=0.11.0,<1.0.0" ] @@ -130,12 +145,16 @@ docs = [ # Configures which build system is used by UV to build the project [build-system] requires = [ - "hatchling>=1.27.0,<2.0.0", - "hatch-vcs>=0.4.0,<1.0.0", - "hatch-fancy-pypi-readme>=25.1.0,<26.0.0" + "hatch-fancy-pypi-readme>=25.1.0,<26.0.0", + "hatch-vcs>=0.5.0,<1.0.0", + "hatchling>=1.27.0,<2.0.0" ] build-backend = "hatchling.build" +# Allows Hatch to install packages from Git repositories +[tool.hatch.metadata] +allow-direct-references = true + # Configures the Hatch-VCS plugin that is used to dynamically determine the version of the project based on the current Git tag [tool.hatch.version] source = "vcs" diff --git a/source/uv.lock b/source/uv.lock index 9c9fa77..c6c6266 100644 --- a/source/uv.lock +++ b/source/uv.lock @@ -207,7 +207,9 @@ umap = [ [package.dev-dependencies] dev = [ { name = "coverage" }, + { name = "hdbscan" }, { name = "mypy" }, + { name = "pybtex" }, { name = "pycodestyle" }, { name = "pydoclint" }, { name = "pylint" }, @@ -222,8 +224,10 @@ dev = [ { name = "sphinxcontrib-datatemplates" }, { name = "tox" }, { name = "tox-uv" }, + { name = "umap-learn" }, ] docs = [ + { name = "pybtex" }, { name = "sphinx" }, { name = "sphinx-copybutton" }, { name = "sphinx-rtd-theme" }, @@ -231,43 +235,53 @@ docs = [ { name = "sphinxcontrib-datatemplates" }, ] linting = [ + { name = "hdbscan" }, { name = "mypy" }, { name = "pycodestyle" }, { name = "pydoclint" }, { name = "pylint" }, { name = "pytest-mypy" }, { name = "scipy-stubs" }, + { name = "umap-learn" }, +] +optional-dependencies = [ + { name = "hdbscan" }, + { name = "umap-learn" }, ] testing = [ { name = "coverage" }, + { name = "hdbscan" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "tox" }, { name = "tox-uv" }, + { name = "umap-learn" }, ] [package.metadata] requires-dist = [ { name = "h5py", specifier = ">=3.13.0,<4.0.0" }, { name = "hdbscan", marker = "extra == 'hdbscan'", specifier = ">=0.8.40,<1.0.0" }, - { name = "matplotlib", specifier = ">=3.10.1,<4.0.0" }, + { name = "matplotlib", specifier = ">=3.10.3,<4.0.0" }, { name = "metrohash-python", specifier = ">=1.1.3.3,<2.0.0" }, - { name = "numpy", specifier = ">=2.2.5,<3.0.0" }, + { name = "numpy", specifier = ">=2.2.6,<3.0.0" }, { name = "scikit-image", specifier = ">=0.25.2,<1.0.0" }, { name = "scikit-learn", specifier = ">=1.6.1,<2.0.0" }, - { name = "scipy", specifier = ">=1.15.2,<2.0.0" }, - { name = "umap-learn", marker = "extra == 'umap'", specifier = ">=0.5.7,<1.0.0" }, + { name = "scipy", specifier = ">=1.15.3,<2.0.0" }, + { name = "umap-learn", marker = "extra == 'umap'", git = "https://github.com/lmcinnes/umap?rev=release-0.5.8" }, ] provides-extras = ["hdbscan", "umap"] [package.metadata.requires-dev] dev = [ - { name = "coverage", specifier = ">=7.8.0,<8.0.0" }, - { name = "mypy", specifier = ">=1.15.0,<2.0.0" }, + { name = "coverage", specifier = ">=7.8.2,<8.0.0" }, + { name = "hdbscan", specifier = ">=0.8.40,<1.0.0" }, + { name = "mypy", specifier = ">=1.16.0,<2.0.0" }, + { name = "pybtex", git = "https://bitbucket.org/pybtex-devs/pybtex?rev=9b97822f5517fc7893456b9827589a003ea7076a" }, { name = "pycodestyle", specifier = ">=2.13.0,<3.0.0" }, { name = "pydoclint", specifier = ">=0.6.6,<1.0.0" }, { name = "pylint", specifier = ">=3.3.7,<4.0.0" }, - { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, + { name = "pytest", specifier = ">=8.4.0,<9.0.0" }, { name = "pytest-cov", specifier = ">=6.1.1,<7.0.0" }, { name = "pytest-mypy", specifier = ">=1.0.1,<2.0.0" }, { name = "scipy-stubs", specifier = ">=1.15.3.0,<2.0.0" }, @@ -277,9 +291,11 @@ dev = [ { name = "sphinxcontrib-bibtex", specifier = ">=2.6.3,<3.0.0" }, { name = "sphinxcontrib-datatemplates", specifier = ">=0.11.0,<1.0.0" }, { name = "tox", specifier = ">=4.26.0,<5.0.0" }, - { name = "tox-uv", specifier = ">=1.25.0,<2.0.0" }, + { name = "tox-uv", specifier = ">=1.26.0,<2.0.0" }, + { name = "umap-learn", git = "https://github.com/lmcinnes/umap?rev=release-0.5.8" }, ] docs = [ + { name = "pybtex", git = "https://bitbucket.org/pybtex-devs/pybtex?rev=9b97822f5517fc7893456b9827589a003ea7076a" }, { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, { name = "sphinx-copybutton", specifier = ">=0.5.2,<1.0.0" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2,<4.0.0" }, @@ -287,69 +303,81 @@ docs = [ { name = "sphinxcontrib-datatemplates", specifier = ">=0.11.0,<1.0.0" }, ] linting = [ - { name = "mypy", specifier = ">=1.15.0,<2.0.0" }, + { name = "hdbscan", specifier = ">=0.8.40,<1.0.0" }, + { name = "mypy", specifier = ">=1.16.0,<2.0.0" }, { name = "pycodestyle", specifier = ">=2.13.0,<3.0.0" }, { name = "pydoclint", specifier = ">=0.6.6,<1.0.0" }, { name = "pylint", specifier = ">=3.3.7,<4.0.0" }, { name = "pytest-mypy", specifier = ">=1.0.1,<2.0.0" }, { name = "scipy-stubs", specifier = ">=1.15.3.0,<2.0.0" }, + { name = "umap-learn", git = "https://github.com/lmcinnes/umap?rev=release-0.5.8" }, +] +optional-dependencies = [ + { name = "hdbscan", specifier = ">=0.8.40,<1.0.0" }, + { name = "umap-learn", git = "https://github.com/lmcinnes/umap?rev=release-0.5.8" }, ] testing = [ - { name = "coverage", specifier = ">=7.8.0,<8.0.0" }, - { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, + { name = "coverage", specifier = ">=7.8.2,<8.0.0" }, + { name = "hdbscan", specifier = ">=0.8.40,<1.0.0" }, + { name = "pytest", specifier = ">=8.4.0,<9.0.0" }, { name = "pytest-cov", specifier = ">=6.1.1,<7.0.0" }, { name = "tox", specifier = ">=4.26.0,<5.0.0" }, - { name = "tox-uv", specifier = ">=1.25.0,<2.0.0" }, + { name = "tox-uv", specifier = ">=1.26.0,<2.0.0" }, + { name = "umap-learn", git = "https://github.com/lmcinnes/umap?rev=release-0.5.8" }, ] [[package]] name = "coverage" -version = "7.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, +version = "7.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/4d/1ff618ee9f134d0de5cc1661582c21a65e06823f41caf801aadf18811a8e/coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9", size = 211692, upload-time = "2025-05-23T11:38:08.485Z" }, + { url = "https://files.pythonhosted.org/packages/96/fa/c3c1b476de96f2bc7a8ca01a9f1fcb51c01c6b60a9d2c3e66194b2bdb4af/coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879", size = 212115, upload-time = "2025-05-23T11:38:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c2/5414c5a1b286c0f3881ae5adb49be1854ac5b7e99011501f81c8c1453065/coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a", size = 244740, upload-time = "2025-05-23T11:38:11.947Z" }, + { url = "https://files.pythonhosted.org/packages/cd/46/1ae01912dfb06a642ef3dd9cf38ed4996fda8fe884dab8952da616f81a2b/coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5", size = 242429, upload-time = "2025-05-23T11:38:13.955Z" }, + { url = "https://files.pythonhosted.org/packages/06/58/38c676aec594bfe2a87c7683942e5a30224791d8df99bcc8439fde140377/coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11", size = 244218, upload-time = "2025-05-23T11:38:15.631Z" }, + { url = "https://files.pythonhosted.org/packages/80/0c/95b1023e881ce45006d9abc250f76c6cdab7134a1c182d9713878dfefcb2/coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a", size = 243865, upload-time = "2025-05-23T11:38:17.622Z" }, + { url = "https://files.pythonhosted.org/packages/57/37/0ae95989285a39e0839c959fe854a3ae46c06610439350d1ab860bf020ac/coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb", size = 242038, upload-time = "2025-05-23T11:38:19.966Z" }, + { url = "https://files.pythonhosted.org/packages/4d/82/40e55f7c0eb5e97cc62cbd9d0746fd24e8caf57be5a408b87529416e0c70/coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54", size = 242567, upload-time = "2025-05-23T11:38:21.912Z" }, + { url = "https://files.pythonhosted.org/packages/f9/35/66a51adc273433a253989f0d9cc7aa6bcdb4855382cf0858200afe578861/coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a", size = 214194, upload-time = "2025-05-23T11:38:23.571Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8f/a543121f9f5f150eae092b08428cb4e6b6d2d134152c3357b77659d2a605/coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975", size = 215109, upload-time = "2025-05-23T11:38:25.137Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/6cc84b68d4f35186463cd7ab1da1169e9abb59870c0f6a57ea6aba95f861/coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53", size = 213521, upload-time = "2025-05-23T11:38:27.123Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876, upload-time = "2025-05-23T11:38:29.01Z" }, + { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130, upload-time = "2025-05-23T11:38:30.675Z" }, + { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176, upload-time = "2025-05-23T11:38:32.395Z" }, + { url = "https://files.pythonhosted.org/packages/a7/52/decbbed61e03b6ffe85cd0fea360a5e04a5a98a7423f292aae62423b8557/coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99", size = 243068, upload-time = "2025-05-23T11:38:33.989Z" }, + { url = "https://files.pythonhosted.org/packages/38/6c/d0e9c0cce18faef79a52778219a3c6ee8e336437da8eddd4ab3dbd8fadff/coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20", size = 245328, upload-time = "2025-05-23T11:38:35.568Z" }, + { url = "https://files.pythonhosted.org/packages/f0/70/f703b553a2f6b6c70568c7e398ed0789d47f953d67fbba36a327714a7bca/coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2", size = 245099, upload-time = "2025-05-23T11:38:37.627Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fb/4cbb370dedae78460c3aacbdad9d249e853f3bc4ce5ff0e02b1983d03044/coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57", size = 243314, upload-time = "2025-05-23T11:38:39.238Z" }, + { url = "https://files.pythonhosted.org/packages/39/9f/1afbb2cb9c8699b8bc38afdce00a3b4644904e6a38c7bf9005386c9305ec/coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f", size = 244489, upload-time = "2025-05-23T11:38:40.845Z" }, + { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366, upload-time = "2025-05-23T11:38:43.551Z" }, + { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165, upload-time = "2025-05-23T11:38:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548, upload-time = "2025-05-23T11:38:46.74Z" }, + { url = "https://files.pythonhosted.org/packages/1a/93/eb6400a745ad3b265bac36e8077fdffcf0268bdbbb6c02b7220b624c9b31/coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca", size = 211898, upload-time = "2025-05-23T11:38:49.066Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7c/bdbf113f92683024406a1cd226a199e4200a2001fc85d6a6e7e299e60253/coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d", size = 212171, upload-time = "2025-05-23T11:38:51.207Z" }, + { url = "https://files.pythonhosted.org/packages/91/22/594513f9541a6b88eb0dba4d5da7d71596dadef6b17a12dc2c0e859818a9/coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85", size = 245564, upload-time = "2025-05-23T11:38:52.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f4/2860fd6abeebd9f2efcfe0fd376226938f22afc80c1943f363cd3c28421f/coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257", size = 242719, upload-time = "2025-05-23T11:38:54.529Z" }, + { url = "https://files.pythonhosted.org/packages/89/60/f5f50f61b6332451520e6cdc2401700c48310c64bc2dd34027a47d6ab4ca/coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108", size = 244634, upload-time = "2025-05-23T11:38:57.326Z" }, + { url = "https://files.pythonhosted.org/packages/3b/70/7f4e919039ab7d944276c446b603eea84da29ebcf20984fb1fdf6e602028/coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0", size = 244824, upload-time = "2025-05-23T11:38:59.421Z" }, + { url = "https://files.pythonhosted.org/packages/26/45/36297a4c0cea4de2b2c442fe32f60c3991056c59cdc3cdd5346fbb995c97/coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050", size = 242872, upload-time = "2025-05-23T11:39:01.049Z" }, + { url = "https://files.pythonhosted.org/packages/a4/71/e041f1b9420f7b786b1367fa2a375703889ef376e0d48de9f5723fb35f11/coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48", size = 244179, upload-time = "2025-05-23T11:39:02.709Z" }, + { url = "https://files.pythonhosted.org/packages/bd/db/3c2bf49bdc9de76acf2491fc03130c4ffc51469ce2f6889d2640eb563d77/coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7", size = 214393, upload-time = "2025-05-23T11:39:05.457Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dc/947e75d47ebbb4b02d8babb1fad4ad381410d5bc9da7cfca80b7565ef401/coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3", size = 215194, upload-time = "2025-05-23T11:39:07.171Z" }, + { url = "https://files.pythonhosted.org/packages/90/31/a980f7df8a37eaf0dc60f932507fda9656b3a03f0abf188474a0ea188d6d/coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7", size = 213580, upload-time = "2025-05-23T11:39:08.862Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6a/25a37dd90f6c95f59355629417ebcb74e1c34e38bb1eddf6ca9b38b0fc53/coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008", size = 212734, upload-time = "2025-05-23T11:39:11.109Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/3a728b3118988725f40950931abb09cd7f43b3c740f4640a59f1db60e372/coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36", size = 212959, upload-time = "2025-05-23T11:39:12.751Z" }, + { url = "https://files.pythonhosted.org/packages/53/3c/212d94e6add3a3c3f412d664aee452045ca17a066def8b9421673e9482c4/coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46", size = 257024, upload-time = "2025-05-23T11:39:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/a4/40/afc03f0883b1e51bbe804707aae62e29c4e8c8bbc365c75e3e4ddeee9ead/coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be", size = 252867, upload-time = "2025-05-23T11:39:17.64Z" }, + { url = "https://files.pythonhosted.org/packages/18/a2/3699190e927b9439c6ded4998941a3c1d6fa99e14cb28d8536729537e307/coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740", size = 255096, upload-time = "2025-05-23T11:39:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/b4/06/16e3598b9466456b718eb3e789457d1a5b8bfb22e23b6e8bbc307df5daf0/coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625", size = 256276, upload-time = "2025-05-23T11:39:21.077Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d5/4b5a120d5d0223050a53d2783c049c311eea1709fa9de12d1c358e18b707/coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b", size = 254478, upload-time = "2025-05-23T11:39:22.838Z" }, + { url = "https://files.pythonhosted.org/packages/ba/85/f9ecdb910ecdb282b121bfcaa32fa8ee8cbd7699f83330ee13ff9bbf1a85/coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199", size = 255255, upload-time = "2025-05-23T11:39:24.644Z" }, + { url = "https://files.pythonhosted.org/packages/50/63/2d624ac7d7ccd4ebbd3c6a9eba9d7fc4491a1226071360d59dd84928ccb2/coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8", size = 215109, upload-time = "2025-05-23T11:39:26.722Z" }, + { url = "https://files.pythonhosted.org/packages/22/5e/7053b71462e970e869111c1853afd642212568a350eba796deefdfbd0770/coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d", size = 216268, upload-time = "2025-05-23T11:39:28.429Z" }, + { url = "https://files.pythonhosted.org/packages/07/69/afa41aa34147655543dbe96994f8a246daf94b361ccf5edfd5df62ce066a/coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b", size = 214071, upload-time = "2025-05-23T11:39:30.55Z" }, + { url = "https://files.pythonhosted.org/packages/69/2f/572b29496d8234e4a7773200dd835a0d32d9e171f2d974f3fe04a9dbc271/coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837", size = 203636, upload-time = "2025-05-23T11:39:52.002Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" }, ] [[package]] @@ -723,7 +751,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.1" +version = "3.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -736,32 +764,32 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335, upload-time = "2025-02-27T19:19:51.038Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669, upload-time = "2025-02-27T19:18:34.346Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996, upload-time = "2025-02-27T19:18:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612, upload-time = "2025-02-27T19:18:39.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258, upload-time = "2025-02-27T19:18:43.217Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896, upload-time = "2025-02-27T19:18:45.852Z" }, - { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281, upload-time = "2025-02-27T19:18:48.919Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488, upload-time = "2025-02-27T19:18:51.436Z" }, - { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264, upload-time = "2025-02-27T19:18:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048, upload-time = "2025-02-27T19:18:56.536Z" }, - { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111, upload-time = "2025-02-27T19:18:59.439Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771, upload-time = "2025-02-27T19:19:01.944Z" }, - { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742, upload-time = "2025-02-27T19:19:04.632Z" }, - { url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112, upload-time = "2025-02-27T19:19:07.59Z" }, - { url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931, upload-time = "2025-02-27T19:19:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422, upload-time = "2025-02-27T19:19:12.738Z" }, - { url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819, upload-time = "2025-02-27T19:19:15.306Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782, upload-time = "2025-02-27T19:19:17.841Z" }, - { url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812, upload-time = "2025-02-27T19:19:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021, upload-time = "2025-02-27T19:19:23.412Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782, upload-time = "2025-02-27T19:19:28.33Z" }, - { url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901, upload-time = "2025-02-27T19:19:31.536Z" }, - { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864, upload-time = "2025-02-27T19:19:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487, upload-time = "2025-02-27T19:19:36.924Z" }, - { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832, upload-time = "2025-02-27T19:19:39.431Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" }, + { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" }, + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, ] [[package]] @@ -781,33 +809,34 @@ sdist = { url = "https://files.pythonhosted.org/packages/95/b5/4002d16e71d6a4b1c [[package]] name = "mypy" -version = "1.15.0" +version = "1.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, + { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload-time = "2025-02-05T03:50:17.287Z" }, - { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload-time = "2025-02-05T03:49:51.21Z" }, - { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload-time = "2025-02-05T03:50:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload-time = "2025-02-05T03:49:42.408Z" }, - { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload-time = "2025-02-05T03:49:07.707Z" }, - { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload-time = "2025-02-05T03:49:54.581Z" }, - { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" }, - { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" }, - { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" }, - { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" }, - { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" }, - { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" }, - { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" }, - { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498, upload-time = "2025-05-29T13:18:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755, upload-time = "2025-05-29T13:34:00.851Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138, upload-time = "2025-05-29T13:32:55.082Z" }, + { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156, upload-time = "2025-05-29T13:19:12.963Z" }, + { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426, upload-time = "2025-05-29T13:20:22.72Z" }, + { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319, upload-time = "2025-05-29T13:21:17.582Z" }, + { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927, upload-time = "2025-05-29T13:35:52.328Z" }, + { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082, upload-time = "2025-05-29T13:35:33.378Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306, upload-time = "2025-05-29T13:21:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5f/b392f7b4f659f5b619ce5994c5c43caab3d80df2296ae54fa888b3d17f5a/mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8", size = 12702764, upload-time = "2025-05-29T13:20:42.826Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c0/7646ef3a00fa39ac9bc0938626d9ff29d19d733011be929cfea59d82d136/mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730", size = 12896233, upload-time = "2025-05-29T13:18:37.446Z" }, + { url = "https://files.pythonhosted.org/packages/6d/38/52f4b808b3fef7f0ef840ee8ff6ce5b5d77381e65425758d515cdd4f5bb5/mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec", size = 9565547, upload-time = "2025-05-29T13:20:02.836Z" }, + { url = "https://files.pythonhosted.org/packages/97/9c/ca03bdbefbaa03b264b9318a98950a9c683e06472226b55472f96ebbc53d/mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b", size = 11059753, upload-time = "2025-05-29T13:18:18.167Z" }, + { url = "https://files.pythonhosted.org/packages/36/92/79a969b8302cfe316027c88f7dc6fee70129490a370b3f6eb11d777749d0/mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0", size = 10073338, upload-time = "2025-05-29T13:19:48.079Z" }, + { url = "https://files.pythonhosted.org/packages/14/9b/a943f09319167da0552d5cd722104096a9c99270719b1afeea60d11610aa/mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b", size = 11827764, upload-time = "2025-05-29T13:46:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/ec/64/ff75e71c65a0cb6ee737287c7913ea155845a556c64144c65b811afdb9c7/mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d", size = 12701356, upload-time = "2025-05-29T13:35:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ad/0e93c18987a1182c350f7a5fab70550852f9fabe30ecb63bfbe51b602074/mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52", size = 12900745, upload-time = "2025-05-29T13:17:24.409Z" }, + { url = "https://files.pythonhosted.org/packages/28/5d/036c278d7a013e97e33f08c047fe5583ab4f1fc47c9a49f985f1cdd2a2d7/mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb", size = 9572200, upload-time = "2025-05-29T13:33:44.92Z" }, + { url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773, upload-time = "2025-05-29T13:35:18.762Z" }, ] [[package]] @@ -857,50 +886,50 @@ wheels = [ [[package]] name = "numpy" -version = "2.2.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload-time = "2025-04-19T23:27:42.561Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475, upload-time = "2025-04-19T22:34:24.174Z" }, - { url = "https://files.pythonhosted.org/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474, upload-time = "2025-04-19T22:34:46.578Z" }, - { url = "https://files.pythonhosted.org/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875, upload-time = "2025-04-19T22:34:56.281Z" }, - { url = "https://files.pythonhosted.org/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176, upload-time = "2025-04-19T22:35:07.518Z" }, - { url = "https://files.pythonhosted.org/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850, upload-time = "2025-04-19T22:35:31.347Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306, upload-time = "2025-04-19T22:35:57.573Z" }, - { url = "https://files.pythonhosted.org/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767, upload-time = "2025-04-19T22:36:22.245Z" }, - { url = "https://files.pythonhosted.org/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515, upload-time = "2025-04-19T22:36:49.822Z" }, - { url = "https://files.pythonhosted.org/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842, upload-time = "2025-04-19T22:37:01.624Z" }, - { url = "https://files.pythonhosted.org/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071, upload-time = "2025-04-19T22:37:21.098Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633, upload-time = "2025-04-19T22:37:52.4Z" }, - { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123, upload-time = "2025-04-19T22:38:15.058Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817, upload-time = "2025-04-19T22:38:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066, upload-time = "2025-04-19T22:38:35.782Z" }, - { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277, upload-time = "2025-04-19T22:38:57.697Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742, upload-time = "2025-04-19T22:39:22.689Z" }, - { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825, upload-time = "2025-04-19T22:39:45.794Z" }, - { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600, upload-time = "2025-04-19T22:40:13.427Z" }, - { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626, upload-time = "2025-04-19T22:40:25.223Z" }, - { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715, upload-time = "2025-04-19T22:40:44.528Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload-time = "2025-04-19T22:41:16.234Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload-time = "2025-04-19T22:41:38.472Z" }, - { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload-time = "2025-04-19T22:41:47.823Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload-time = "2025-04-19T22:41:58.689Z" }, - { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload-time = "2025-04-19T22:42:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload-time = "2025-04-19T22:42:44.433Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload-time = "2025-04-19T22:43:09.928Z" }, - { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload-time = "2025-04-19T22:43:36.983Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload-time = "2025-04-19T22:47:10.523Z" }, - { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload-time = "2025-04-19T22:47:30.253Z" }, - { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload-time = "2025-04-19T22:44:09.251Z" }, - { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload-time = "2025-04-19T22:44:31.383Z" }, - { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload-time = "2025-04-19T22:44:40.361Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload-time = "2025-04-19T22:44:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload-time = "2025-04-19T22:45:12.451Z" }, - { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload-time = "2025-04-19T22:45:37.734Z" }, - { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload-time = "2025-04-19T22:46:01.908Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload-time = "2025-04-19T22:46:28.585Z" }, - { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload-time = "2025-04-19T22:46:39.949Z" }, - { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload-time = "2025-04-19T22:47:00.147Z" }, +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +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/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" }, ] [[package]] @@ -924,6 +953,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + [[package]] name = "pillow" version = "11.2.1" @@ -1004,15 +1042,10 @@ wheels = [ [[package]] name = "pybtex" version = "0.24.0" -source = { registry = "https://pypi.org/simple" } +source = { git = "https://bitbucket.org/pybtex-devs/pybtex?rev=9b97822f5517fc7893456b9827589a003ea7076a#9b97822f5517fc7893456b9827589a003ea7076a" } dependencies = [ { name = "latexcodec" }, { name = "pyyaml" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/9b/fd39836a6397fb363446d83075a7b9c2cc562f4c449292e039ed36084376/pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755", size = 402879, upload-time = "2021-01-17T20:02:27.328Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/5f/40d8e90f985a05133a8895fc454c6127ecec3de8b095dd35bba91382f803/pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f", size = 561354, upload-time = "2021-01-17T20:02:23.696Z" }, ] [[package]] @@ -1116,17 +1149,18 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/aa/405082ce2749be5398045152251ac69c0f3578c7077efc53431303af97ce/pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6", size = 1515232, upload-time = "2025-06-02T17:36:30.03Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/2f/de/afa024cbe022b1b318a3d224125aa24939e99b4ff6f22e0ba639a2eaee47/pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e", size = 363797, upload-time = "2025-06-02T17:36:27.859Z" }, ] [[package]] @@ -1296,49 +1330,49 @@ wheels = [ [[package]] name = "scipy" -version = "1.15.2" +version = "1.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316, upload-time = "2025-02-17T00:42:24.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651, upload-time = "2025-02-17T00:30:31.09Z" }, - { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038, upload-time = "2025-02-17T00:30:40.219Z" }, - { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518, upload-time = "2025-02-17T00:30:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523, upload-time = "2025-02-17T00:30:56.002Z" }, - { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547, upload-time = "2025-02-17T00:31:07.599Z" }, - { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077, upload-time = "2025-02-17T00:31:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657, upload-time = "2025-02-17T00:31:22.041Z" }, - { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857, upload-time = "2025-02-17T00:31:29.836Z" }, - { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654, upload-time = "2025-02-17T00:31:43.65Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184, upload-time = "2025-02-17T00:31:50.623Z" }, - { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558, upload-time = "2025-02-17T00:31:56.721Z" }, - { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211, upload-time = "2025-02-17T00:32:03.042Z" }, - { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260, upload-time = "2025-02-17T00:32:07.847Z" }, - { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095, upload-time = "2025-02-17T00:32:14.565Z" }, - { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371, upload-time = "2025-02-17T00:32:21.411Z" }, - { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390, upload-time = "2025-02-17T00:32:29.421Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276, upload-time = "2025-02-17T00:32:37.431Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317, upload-time = "2025-02-17T00:32:45.47Z" }, - { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587, upload-time = "2025-02-17T00:32:53.196Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266, upload-time = "2025-02-17T00:32:59.318Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768, upload-time = "2025-02-17T00:33:04.091Z" }, - { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719, upload-time = "2025-02-17T00:33:08.909Z" }, - { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195, upload-time = "2025-02-17T00:33:15.352Z" }, - { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404, upload-time = "2025-02-17T00:33:22.21Z" }, - { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011, upload-time = "2025-02-17T00:33:29.446Z" }, - { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406, upload-time = "2025-02-17T00:33:39.019Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243, upload-time = "2025-02-17T00:34:51.024Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286, upload-time = "2025-02-17T00:33:47.62Z" }, - { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634, upload-time = "2025-02-17T00:33:54.131Z" }, - { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179, upload-time = "2025-02-17T00:33:59.948Z" }, - { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412, upload-time = "2025-02-17T00:34:06.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867, upload-time = "2025-02-17T00:34:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009, upload-time = "2025-02-17T00:34:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159, upload-time = "2025-02-17T00:34:26.724Z" }, - { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566, upload-time = "2025-02-17T00:34:34.512Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705, upload-time = "2025-02-17T00:34:43.619Z" }, +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/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]] @@ -1595,16 +1629,16 @@ wheels = [ [[package]] name = "tox-uv" -version = "1.25.0" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "tox" }, { name = "uv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/3a/3e445f25978a716ba6674f33f687d9336d0312086a277a778a5e9e9220d7/tox_uv-1.25.0.tar.gz", hash = "sha256:59ee5e694c41fef7bbcf058f22a5f9b6a8509698def2ea60c08554f4e36b9fcc", size = 21114, upload-time = "2025-02-21T16:37:51.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/da/37790b4a176f05b0ec7a699f54979078fc726f743640aa5c10c551c27edb/tox_uv-1.26.0.tar.gz", hash = "sha256:5045880c467eed58a98f7eaa7fe286b7ef688e2c56f2123d53e275011495c381", size = 21523, upload-time = "2025-05-27T14:51:42.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a7/f5c29e0e6faaccefcab607f672b176927144e9412c8183d21301ea2a6f6c/tox_uv-1.25.0-py3-none-any.whl", hash = "sha256:50cfe7795dcd49b2160d7d65b5ece8717f38cfedc242c852a40ec0a71e159bf7", size = 16431, upload-time = "2025-02-21T16:37:49.657Z" }, + { url = "https://files.pythonhosted.org/packages/46/b8/04c5cb83da072a3f96d357d68a551f5e97e162573c2011a09437df995811/tox_uv-1.26.0-py3-none-any.whl", hash = "sha256:894b2e7274fd6131c3bd1012813edc858753cad67727050c21cd973a08e691c8", size = 16562, upload-time = "2025-05-27T14:51:40.803Z" }, ] [[package]] @@ -1630,8 +1664,8 @@ wheels = [ [[package]] name = "umap-learn" -version = "0.5.7" -source = { registry = "https://pypi.org/simple" } +version = "0.5.8" +source = { git = "https://github.com/lmcinnes/umap?rev=release-0.5.8#63903695b996e2b68c67e00fdda6eedb7a233189" } dependencies = [ { name = "numba" }, { name = "numpy" }, @@ -1640,10 +1674,6 @@ dependencies = [ { name = "scipy" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/d4/9ed627905f7993349671283b3c5bf2d9f543ef79229fa1c7e01324eb900c/umap-learn-0.5.7.tar.gz", hash = "sha256:b2a97973e4c6ffcebf241100a8de589a4c84126a832ab40f296c6d9fcc5eb19e", size = 92680, upload-time = "2024-10-28T18:05:57.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/8f/671c0e1f2572ba625cbcc1faeba9435e00330c3d6962858711445cf1e817/umap_learn-0.5.7-py3-none-any.whl", hash = "sha256:6a7e0be2facfa365a5ed6588447102bdbef32a0ef449535c25c97ea7e680073c", size = 88815, upload-time = "2024-10-28T18:05:55.333Z" }, -] [[package]] name = "urllib3" diff --git a/tests/linters/.pycodestyle b/tests/linters/.pycodestyle index 07cc9c0..1e04cb9 100644 --- a/tests/linters/.pycodestyle +++ b/tests/linters/.pycodestyle @@ -5,3 +5,14 @@ # Sets the maximum line length for both code and docstrings to 150 characters max-line-length = 150 max-doc-length = 150 + +# Selects the specified list of errors and warnings to be enforced +# W503: The line break should occur before a binary operator, which is in contrast to W504, which requires the line break to occur after a binary +# operator; we prefer W503 +select = W503 + +# Ignores the specified list of errors and warnings +# E731: Instead of assigning a lambda expression to a variable, it should be defined using the "def" keyword; it is, however, sometimes useful to +# assign a lambda expression to a variable, especially in the unit tests +# E131: The continuation line is unaligned for hanging indentation; we do not use hanging indentation in the codebase +ignore = E131,E731 diff --git a/tests/linters/.pylintrc b/tests/linters/.pylintrc index 170361b..08ea50e 100644 --- a/tests/linters/.pylintrc +++ b/tests/linters/.pylintrc @@ -13,14 +13,14 @@ ignore=version.py # Maximum number of arguments for a function/method max-args=10 -# Maximum number of positional arguments for a function/method -max-positional-arguments=10 +# Maximum number of branch for function/method body +max-branches=16 # Maximum number of locals for a function/method body max-locals=40 -# Maximum number of statements in a function/method body -max-statements=75 +# Maximum number of positional arguments for a function/method +max-positional-arguments=10 # Maximum number of public methods for a class (see R0904) max-public-methods=30 @@ -28,6 +28,9 @@ max-public-methods=30 # Maximum number of return/yield statements in a function/method body max-returns=10 +# Maximum number of statements in a function/method body +max-statements=75 + # Minimum number of public methods for a class (see R0903) min-public-methods=0 @@ -38,3 +41,21 @@ min-public-methods=0 # Maximum number of characters on a single line max-line-length=150 + + +# Configures which messages are enabled or disabled in the Pylint static code analysis tool +[MESSAGES CONTROL] + +# Disable the message, report, category or checker with the given ID(s) +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero, + use-symbolic-message-instead, + unnecessary-lambda-assignment # Sometimes it is useful to assign a lambda expression to a variable, instead of declaring a + # function using the "def" keyword, especially in the unit tests diff --git a/tests/linters/cspell/.cspell.json b/tests/linters/cspell/.cspell.json index 900d565..5811756 100644 --- a/tests/linters/cspell/.cspell.json +++ b/tests/linters/cspell/.cspell.json @@ -24,6 +24,7 @@ "**/.cspell.json", "**/.git/!(COMMIT_EDITMSG)", "**/*.svg", + "**/corelay/version.py", "**/docs/build", "**/docs/doctree", "**/node_modules", @@ -80,6 +81,7 @@ "absspectral", "addopts", "adfg", + "allclose", "Anders", "Anson", "arccos", @@ -90,6 +92,7 @@ "bibfiles", "booksubtitle", "booktitle", + "booleaness", "braycurtis", "buildinfo", "bysource", @@ -123,6 +126,7 @@ "eigsh", "eigval", "eigvec", + "envlist", "envname", "eprintclass", "eprinttype", @@ -176,6 +180,7 @@ "Müller", "ndarray", "Neumann", + "newaxis", "nitpicky", "notest", "nsri", @@ -194,6 +199,7 @@ "pydoclint", "pylintrc", "randn", + "RBF", "rcfile", "reftypes", "regionref", @@ -220,6 +226,7 @@ "tamasfe", "testpaths", "texlive", + "toarray", "toctree", "todense", "TSNE", @@ -228,6 +235,7 @@ "Ulrike", "umap", "urldate", + "varnames", "vimrc", "vsicons", "Wäldchen", diff --git a/tests/linters/cspell/package-lock.json b/tests/linters/cspell/package-lock.json index fb4e777..fc90ad0 100644 --- a/tests/linters/cspell/package-lock.json +++ b/tests/linters/cspell/package-lock.json @@ -1,306 +1,312 @@ { - "name": "@virelay/cspell-config", - "version": "0.5.0", + "name": "@corelay/cspell-config", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@virelay/cspell-config", - "version": "0.5.0", + "name": "@corelay/cspell-config", + "version": "0.3.0", "license": "AGPL-3.0-or-later", "dependencies": { - "cspell": "^8.16.1" + "cspell": "^9.0.2" } }, "node_modules/@cspell/cspell-bundled-dicts": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.16.1.tgz", - "integrity": "sha512-EkbtoYpmiN9YPfcOoPcMnIrJBZh13mun64jPyyaYhrPPToiU5+CisZ7ZKUBGnqNaatuciMUxwIudhanQJ7Yhnw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.0.2.tgz", + "integrity": "sha512-gGFSfVIvYtO95O3Yhcd1o0sOZHjVaCPwYq3MnaNsBBzaMviIZli4FZW9Z+XNKsgo1zRzbl2SdOXJPP0VcyAY0A==", "license": "MIT", "dependencies": { - "@cspell/dict-ada": "^4.0.5", - "@cspell/dict-al": "^1.0.3", - "@cspell/dict-aws": "^4.0.7", - "@cspell/dict-bash": "^4.1.8", - "@cspell/dict-companies": "^3.1.7", - "@cspell/dict-cpp": "^6.0.2", - "@cspell/dict-cryptocurrencies": "^5.0.3", - "@cspell/dict-csharp": "^4.0.5", - "@cspell/dict-css": "^4.0.16", - "@cspell/dict-dart": "^2.2.4", - "@cspell/dict-django": "^4.1.3", - "@cspell/dict-docker": "^1.1.11", - "@cspell/dict-dotnet": "^5.0.8", - "@cspell/dict-elixir": "^4.0.6", - "@cspell/dict-en_us": "^4.3.28", - "@cspell/dict-en-common-misspellings": "^2.0.7", - "@cspell/dict-en-gb": "1.1.33", - "@cspell/dict-filetypes": "^3.0.8", - "@cspell/dict-flutter": "^1.0.3", - "@cspell/dict-fonts": "^4.0.3", - "@cspell/dict-fsharp": "^1.0.4", - "@cspell/dict-fullstack": "^3.2.3", - "@cspell/dict-gaming-terms": "^1.0.8", - "@cspell/dict-git": "^3.0.3", - "@cspell/dict-golang": "^6.0.17", - "@cspell/dict-google": "^1.0.4", - "@cspell/dict-haskell": "^4.0.4", - "@cspell/dict-html": "^4.0.10", + "@cspell/dict-ada": "^4.1.0", + "@cspell/dict-al": "^1.1.0", + "@cspell/dict-aws": "^4.0.10", + "@cspell/dict-bash": "^4.2.0", + "@cspell/dict-companies": "^3.2.1", + "@cspell/dict-cpp": "^6.0.8", + "@cspell/dict-cryptocurrencies": "^5.0.4", + "@cspell/dict-csharp": "^4.0.6", + "@cspell/dict-css": "^4.0.17", + "@cspell/dict-dart": "^2.3.0", + "@cspell/dict-data-science": "^2.0.8", + "@cspell/dict-django": "^4.1.4", + "@cspell/dict-docker": "^1.1.14", + "@cspell/dict-dotnet": "^5.0.9", + "@cspell/dict-elixir": "^4.0.7", + "@cspell/dict-en_us": "^4.4.8", + "@cspell/dict-en-common-misspellings": "^2.0.11", + "@cspell/dict-en-gb-mit": "^3.0.6", + "@cspell/dict-filetypes": "^3.0.12", + "@cspell/dict-flutter": "^1.1.0", + "@cspell/dict-fonts": "^4.0.4", + "@cspell/dict-fsharp": "^1.1.0", + "@cspell/dict-fullstack": "^3.2.6", + "@cspell/dict-gaming-terms": "^1.1.1", + "@cspell/dict-git": "^3.0.5", + "@cspell/dict-golang": "^6.0.21", + "@cspell/dict-google": "^1.0.8", + "@cspell/dict-haskell": "^4.0.5", + "@cspell/dict-html": "^4.0.11", "@cspell/dict-html-symbol-entities": "^4.0.3", - "@cspell/dict-java": "^5.0.10", - "@cspell/dict-julia": "^1.0.4", - "@cspell/dict-k8s": "^1.0.9", + "@cspell/dict-java": "^5.0.11", + "@cspell/dict-julia": "^1.1.0", + "@cspell/dict-k8s": "^1.0.10", + "@cspell/dict-kotlin": "^1.1.0", "@cspell/dict-latex": "^4.0.3", - "@cspell/dict-lorem-ipsum": "^4.0.3", - "@cspell/dict-lua": "^4.0.6", - "@cspell/dict-makefile": "^1.0.3", - "@cspell/dict-markdown": "^2.0.7", - "@cspell/dict-monkeyc": "^1.0.9", - "@cspell/dict-node": "^5.0.5", - "@cspell/dict-npm": "^5.1.14", - "@cspell/dict-php": "^4.0.13", - "@cspell/dict-powershell": "^5.0.13", - "@cspell/dict-public-licenses": "^2.0.11", - "@cspell/dict-python": "^4.2.12", - "@cspell/dict-r": "^2.0.4", - "@cspell/dict-ruby": "^5.0.7", - "@cspell/dict-rust": "^4.0.10", - "@cspell/dict-scala": "^5.0.6", - "@cspell/dict-software-terms": "^4.1.17", - "@cspell/dict-sql": "^2.1.8", - "@cspell/dict-svelte": "^1.0.5", - "@cspell/dict-swift": "^2.0.4", - "@cspell/dict-terraform": "^1.0.6", - "@cspell/dict-typescript": "^3.1.11", - "@cspell/dict-vue": "^3.0.3" + "@cspell/dict-lorem-ipsum": "^4.0.4", + "@cspell/dict-lua": "^4.0.7", + "@cspell/dict-makefile": "^1.0.4", + "@cspell/dict-markdown": "^2.0.10", + "@cspell/dict-monkeyc": "^1.0.10", + "@cspell/dict-node": "^5.0.7", + "@cspell/dict-npm": "^5.2.3", + "@cspell/dict-php": "^4.0.14", + "@cspell/dict-powershell": "^5.0.14", + "@cspell/dict-public-licenses": "^2.0.13", + "@cspell/dict-python": "^4.2.18", + "@cspell/dict-r": "^2.1.0", + "@cspell/dict-ruby": "^5.0.8", + "@cspell/dict-rust": "^4.0.11", + "@cspell/dict-scala": "^5.0.7", + "@cspell/dict-shell": "^1.1.0", + "@cspell/dict-software-terms": "^5.0.9", + "@cspell/dict-sql": "^2.2.0", + "@cspell/dict-svelte": "^1.0.6", + "@cspell/dict-swift": "^2.0.5", + "@cspell/dict-terraform": "^1.1.1", + "@cspell/dict-typescript": "^3.2.1", + "@cspell/dict-vue": "^3.0.4" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/cspell-json-reporter": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.16.1.tgz", - "integrity": "sha512-ue1paJ2OE2BjIBQHXFMHnFqJL5xMrE/TLveOntDSCKJw7edCGP4XJA6Q0ZfUgR/ZAP3SYKNPkajEWbDTMfG+XA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.0.2.tgz", + "integrity": "sha512-Hy9hKG53cFhLwiSZuRVAd5YfBb5pPj3V2Val69TW1j4+sy3podewqm4sb3RqoB01LcDkLI/mOeMwHz1xyIjfoA==", "license": "MIT", "dependencies": { - "@cspell/cspell-types": "8.16.1" + "@cspell/cspell-types": "9.0.2" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/cspell-pipe": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.16.1.tgz", - "integrity": "sha512-6N+QZ3y65JRgGrQhZHmaBHESR+nC0J8nySGaYKclit8yk3jLZ/ORw9aoSGIj+dMPzImkNEDh+C1B1zdV4X8W6A==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.0.2.tgz", + "integrity": "sha512-M1e+u3dyGCJicSZ16xmoVut4pI8ynfqILYiDAYC9+rbn04wJdnWD46ElIZnRriFXx7fu/UsUEexu3lFaqKVGEg==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/cspell-resolver": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.16.1.tgz", - "integrity": "sha512-CfVI2JFMwh9/n1QuU9niEONbYCX1XGKqmyCcHQUzAapSqGzbAmFrRFnvyKwNL+mmy1bxli9EZV8f5vBco26f9Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.0.2.tgz", + "integrity": "sha512-JkMQb+hcEyZ2ALvEeJvfxoIblRpZlnek50Ew5sLSSZciRuhNvQZS5+Apwt1GXHitTo8/bqXFxABNP36O++YAwA==", "license": "MIT", "dependencies": { "global-directory": "^4.0.1" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/cspell-service-bus": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.16.1.tgz", - "integrity": "sha512-URaralJKcdHZH/Lr25L28GJo2Ub07adHPPhOL83BvmPyGkboehmz5arjNrgQFwS+IvGjHLdp5uzEJd0xyeHGdw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.0.2.tgz", + "integrity": "sha512-OjfZ3vnBjmkctC9xs/87/9bx/3kZYUPJkWsZxzfH4rla/HeIUrm9UZlDqCibhWifhPHrDdV9hDW5QEGXkYR2hw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/cspell-types": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.16.1.tgz", - "integrity": "sha512-B8bHlBaDSMDMEq++H8qO9osKUkzWUrP4CgWQyRqlXZ9EOdnJ469Tp1wghcQ7DezII3aXYrHiVKsUYY9VvjkhIg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.0.2.tgz", + "integrity": "sha512-RioULo34qbUXuCCLi/DCDxdb++Nm1ospNXzVkKZrSvTG4AjkC95ZhfIOp9jbGSWqL2PGdaHVXgG77EyQbAk5xA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/dict-ada": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.0.5.tgz", - "integrity": "sha512-6/RtZ/a+lhFVmrx/B7bfP7rzC4yjEYe8o74EybXcvu4Oue6J4Ey2WSYj96iuodloj1LWrkNCQyX5h4Pmcj0Iag==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.0.tgz", + "integrity": "sha512-7SvmhmX170gyPd+uHXrfmqJBY5qLcCX8kTGURPVeGxmt8XNXT75uu9rnZO+jwrfuU2EimNoArdVy5GZRGljGNg==", "license": "MIT" }, "node_modules/@cspell/dict-al": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.0.3.tgz", - "integrity": "sha512-V1HClwlfU/qwSq2Kt+MkqRAsonNu3mxjSCDyGRecdLGIHmh7yeEeaxqRiO/VZ4KP+eVSiSIlbwrb5YNFfxYZbw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.0.tgz", + "integrity": "sha512-PtNI1KLmYkELYltbzuoztBxfi11jcE9HXBHCpID2lou/J4VMYKJPNqe4ZjVzSI9NYbMnMnyG3gkbhIdx66VSXg==", "license": "MIT" }, "node_modules/@cspell/dict-aws": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.7.tgz", - "integrity": "sha512-PoaPpa2NXtSkhGIMIKhsJUXB6UbtTt6Ao3x9JdU9kn7fRZkwD4RjHDGqulucIOz7KeEX/dNRafap6oK9xHe4RA==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.10.tgz", + "integrity": "sha512-0qW4sI0GX8haELdhfakQNuw7a2pnWXz3VYQA2MpydH2xT2e6EN9DWFpKAi8DfcChm8MgDAogKkoHtIo075iYng==", "license": "MIT" }, "node_modules/@cspell/dict-bash": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.1.8.tgz", - "integrity": "sha512-I2CM2pTNthQwW069lKcrVxchJGMVQBzru2ygsHCwgidXRnJL/NTjAPOFTxN58Jc1bf7THWghfEDyKX/oyfc0yg==", - "license": "MIT" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.0.tgz", + "integrity": "sha512-HOyOS+4AbCArZHs/wMxX/apRkjxg6NDWdt0jF9i9XkvJQUltMwEhyA2TWYjQ0kssBsnof+9amax2lhiZnh3kCg==", + "license": "MIT", + "dependencies": { + "@cspell/dict-shell": "1.1.0" + } }, "node_modules/@cspell/dict-companies": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.1.7.tgz", - "integrity": "sha512-ncVs/efuAkP1/tLDhWbXukBjgZ5xOUfe03neHMWsE8zvXXc5+Lw6TX5jaJXZLOoES/f4j4AhRE20jsPCF5pm+A==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.1.tgz", + "integrity": "sha512-ryaeJ1KhTTKL4mtinMtKn8wxk6/tqD4vX5tFP+Hg89SiIXmbMk5vZZwVf+eyGUWJOyw5A1CVj9EIWecgoi+jYQ==", "license": "MIT" }, "node_modules/@cspell/dict-cpp": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.2.tgz", - "integrity": "sha512-yw5eejWvY4bAnc6LUA44m4WsFwlmgPt2uMSnO7QViGMBDuoeopMma4z9XYvs4lSjTi8fIJs/A1YDfM9AVzb8eg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.8.tgz", + "integrity": "sha512-BzurRZilWqaJt32Gif6/yCCPi+FtrchjmnehVEIFzbWyeBd/VOUw77IwrEzehZsu5cRU91yPWuWp5fUsKfDAXA==", "license": "MIT" }, "node_modules/@cspell/dict-cryptocurrencies": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.3.tgz", - "integrity": "sha512-bl5q+Mk+T3xOZ12+FG37dB30GDxStza49Rmoax95n37MTLksk9wBo1ICOlPJ6PnDUSyeuv4SIVKgRKMKkJJglA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.4.tgz", + "integrity": "sha512-6iFu7Abu+4Mgqq08YhTKHfH59mpMpGTwdzDB2Y8bbgiwnGFCeoiSkVkgLn1Kel2++hYcZ8vsAW/MJS9oXxuMag==", "license": "MIT" }, "node_modules/@cspell/dict-csharp": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.5.tgz", - "integrity": "sha512-c/sFnNgtRwRJxtC3JHKkyOm+U3/sUrltFeNwml9VsxKBHVmvlg4tk4ar58PdpW9/zTlGUkWi2i85//DN1EsUCA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.6.tgz", + "integrity": "sha512-w/+YsqOknjQXmIlWDRmkW+BHBPJZ/XDrfJhZRQnp0wzpPOGml7W0q1iae65P2AFRtTdPKYmvSz7AL5ZRkCnSIw==", "license": "MIT" }, "node_modules/@cspell/dict-css": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.16.tgz", - "integrity": "sha512-70qu7L9z/JR6QLyJPk38fNTKitlIHnfunx0wjpWQUQ8/jGADIhMCrz6hInBjqPNdtGpYm8d1dNFyF8taEkOgrQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.17.tgz", + "integrity": "sha512-2EisRLHk6X/PdicybwlajLGKF5aJf4xnX2uuG5lexuYKt05xV/J/OiBADmi8q9obhxf1nesrMQbqAt+6CsHo/w==", "license": "MIT" }, "node_modules/@cspell/dict-dart": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.2.4.tgz", - "integrity": "sha512-of/cVuUIZZK/+iqefGln8G3bVpfyN6ZtH+LyLkHMoR5tEj+2vtilGNk9ngwyR8L4lEqbKuzSkOxgfVjsXf5PsQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.0.tgz", + "integrity": "sha512-1aY90lAicek8vYczGPDKr70pQSTQHwMFLbmWKTAI6iavmb1fisJBS1oTmMOKE4ximDf86MvVN6Ucwx3u/8HqLg==", "license": "MIT" }, "node_modules/@cspell/dict-data-science": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.5.tgz", - "integrity": "sha512-nNSILXmhSJox9/QoXICPQgm8q5PbiSQP4afpbkBqPi/u/b3K9MbNH5HvOOa6230gxcGdbZ9Argl2hY/U8siBlg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.8.tgz", + "integrity": "sha512-uyAtT+32PfM29wRBeAkUSbkytqI8bNszNfAz2sGPtZBRmsZTYugKMEO9eDjAIE/pnT9CmbjNuoiXhk+Ss4fCOg==", "license": "MIT" }, "node_modules/@cspell/dict-django": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.3.tgz", - "integrity": "sha512-yBspeL3roJlO0a1vKKNaWABURuHdHZ9b1L8d3AukX0AsBy9snSggc8xCavPmSzNfeMDXbH+1lgQiYBd3IW03fg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.4.tgz", + "integrity": "sha512-fX38eUoPvytZ/2GA+g4bbdUtCMGNFSLbdJJPKX2vbewIQGfgSFJKY56vvcHJKAvw7FopjvgyS/98Ta9WN1gckg==", "license": "MIT" }, "node_modules/@cspell/dict-docker": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.11.tgz", - "integrity": "sha512-s0Yhb16/R+UT1y727ekbR/itWQF3Qz275DR1ahOa66wYtPjHUXmhM3B/LT3aPaX+hD6AWmK23v57SuyfYHUjsw==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.14.tgz", + "integrity": "sha512-p6Qz5mokvcosTpDlgSUREdSbZ10mBL3ndgCdEKMqjCSZJFdfxRdNdjrGER3lQ6LMq5jGr1r7nGXA0gvUJK80nw==", "license": "MIT" }, "node_modules/@cspell/dict-dotnet": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.8.tgz", - "integrity": "sha512-MD8CmMgMEdJAIPl2Py3iqrx3B708MbCIXAuOeZ0Mzzb8YmLmiisY7QEYSZPg08D7xuwARycP0Ki+bb0GAkFSqg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.9.tgz", + "integrity": "sha512-JGD6RJW5sHtO5lfiJl11a5DpPN6eKSz5M1YBa1I76j4dDOIqgZB6rQexlDlK1DH9B06X4GdDQwdBfnpAB0r2uQ==", "license": "MIT" }, "node_modules/@cspell/dict-elixir": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.6.tgz", - "integrity": "sha512-TfqSTxMHZ2jhiqnXlVKM0bUADtCvwKQv2XZL/DI0rx3doG8mEMS8SGPOmiyyGkHpR/pGOq18AFH3BEm4lViHIw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.7.tgz", + "integrity": "sha512-MAUqlMw73mgtSdxvbAvyRlvc3bYnrDqXQrx5K9SwW8F7fRYf9V4vWYFULh+UWwwkqkhX9w03ZqFYRTdkFku6uA==", "license": "MIT" }, "node_modules/@cspell/dict-en_us": { - "version": "4.3.28", - "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.3.28.tgz", - "integrity": "sha512-BN1PME7cOl7DXRQJ92pEd1f0Xk5sqjcDfThDGkKcsgwbSOY7KnTc/czBW6Pr3WXIchIm6cT12KEfjNqx7U7Rrw==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.9.tgz", + "integrity": "sha512-5gjqpUwhE+qP9A9wxD1+MGGJ3DNqTgSpiOsS10cGJfV4p/Z194XkDUZrUrJsnJA/3fsCZHAzcNWh8m0bw1v++A==", "license": "MIT" }, "node_modules/@cspell/dict-en-common-misspellings": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.0.7.tgz", - "integrity": "sha512-qNFo3G4wyabcwnM+hDrMYKN9vNVg/k9QkhqSlSst6pULjdvPyPs1mqz1689xO/v9t8e6sR4IKc3CgUXDMTYOpA==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.0.11.tgz", + "integrity": "sha512-xFQjeg0wFHh9sFhshpJ+5BzWR1m9Vu8pD0CGPkwZLK9oii8AD8RXNchabLKy/O5VTLwyqPOi9qpyp1cxm3US4Q==", "license": "CC BY-SA 4.0" }, - "node_modules/@cspell/dict-en-gb": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", - "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", + "node_modules/@cspell/dict-en-gb-mit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.0.7.tgz", + "integrity": "sha512-fdZbu4jbkzjjTO0jPBGINwQwzNFGapMnhH9D4mDa4UzGGyQFVRx6n/FFwxnfs7CXbuCV6UFSwjHZEAB8pfWn0A==", "license": "MIT" }, "node_modules/@cspell/dict-filetypes": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.8.tgz", - "integrity": "sha512-D3N8sm/iptzfVwsib/jvpX+K/++rM8SRpLDFUaM4jxm8EyGmSIYRbKZvdIv5BkAWmMlTWoRqlLn7Yb1b11jKJg==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.12.tgz", + "integrity": "sha512-+ds5wgNdlUxuJvhg8A1TjuSpalDFGCh7SkANCWvIplg6QZPXL4j83lqxP7PgjHpx7PsBUS7vw0aiHPjZy9BItw==", "license": "MIT" }, "node_modules/@cspell/dict-flutter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.0.3.tgz", - "integrity": "sha512-52C9aUEU22ptpgYh6gQyIdA4MP6NPwzbEqndfgPh3Sra191/kgs7CVqXiO1qbtZa9gnYHUoVApkoxRE7mrXHfg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.0.tgz", + "integrity": "sha512-3zDeS7zc2p8tr9YH9tfbOEYfopKY/srNsAa+kE3rfBTtQERAZeOhe5yxrnTPoufctXLyuUtcGMUTpxr3dO0iaA==", "license": "MIT" }, "node_modules/@cspell/dict-fonts": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.3.tgz", - "integrity": "sha512-sPd17kV5qgYXLteuHFPn5mbp/oCHKgitNfsZLFC3W2fWEgZlhg4hK+UGig3KzrYhhvQ8wBnmZrAQm0TFKCKzsA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.4.tgz", + "integrity": "sha512-cHFho4hjojBcHl6qxidl9CvUb492IuSk7xIf2G2wJzcHwGaCFa2o3gRcxmIg1j62guetAeDDFELizDaJlVRIOg==", "license": "MIT" }, "node_modules/@cspell/dict-fsharp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.0.4.tgz", - "integrity": "sha512-G5wk0o1qyHUNi9nVgdE1h5wl5ylq7pcBjX8vhjHcO4XBq20D5eMoXjwqMo/+szKAqzJ+WV3BgAL50akLKrT9Rw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.0.tgz", + "integrity": "sha512-oguWmHhGzgbgbEIBKtgKPrFSVAFtvGHaQS0oj+vacZqMObwkapcTGu7iwf4V3Bc2T3caf0QE6f6rQfIJFIAVsw==", "license": "MIT" }, "node_modules/@cspell/dict-fullstack": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.3.tgz", - "integrity": "sha512-62PbndIyQPH11mAv0PyiyT0vbwD0AXEocPpHlCHzfb5v9SspzCCbzQ/LIBiFmyRa+q5LMW35CnSVu6OXdT+LKg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.6.tgz", + "integrity": "sha512-cSaq9rz5RIU9j+0jcF2vnKPTQjxGXclntmoNp4XB7yFX2621PxJcekGjwf/lN5heJwVxGLL9toR0CBlGKwQBgA==", "license": "MIT" }, "node_modules/@cspell/dict-gaming-terms": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.0.8.tgz", - "integrity": "sha512-7OL0zTl93WFWhhtpXFrtm9uZXItC3ncAs8d0iQDMMFVNU1rBr6raBNxJskxE5wx2Ant12fgI66ZGVagXfN+yfA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.1.tgz", + "integrity": "sha512-tb8GFxjTLDQstkJcJ90lDqF4rKKlMUKs5/ewePN9P+PYRSehqDpLI5S5meOfPit8LGszeOrjUdBQ4zXo7NpMyQ==", "license": "MIT" }, "node_modules/@cspell/dict-git": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.3.tgz", - "integrity": "sha512-LSxB+psZ0qoj83GkyjeEH/ZViyVsGEF/A6BAo8Nqc0w0HjD2qX/QR4sfA6JHUgQ3Yi/ccxdK7xNIo67L2ScW5A==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.5.tgz", + "integrity": "sha512-I7l86J2nOcpBY0OcwXLTGMbcXbEE7nxZme9DmYKrNgmt35fcLu+WKaiXW7P29V+lIXjJo/wKrEDY+wUEwVuABQ==", "license": "MIT" }, "node_modules/@cspell/dict-golang": { - "version": "6.0.17", - "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.17.tgz", - "integrity": "sha512-uDDLEJ/cHdLiqPw4+5BnmIo2i/TSR+uDvYd6JlBjTmjBKpOCyvUgYRztH7nv5e7virsN5WDiUWah4/ATQGz4Pw==", + "version": "6.0.21", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.21.tgz", + "integrity": "sha512-D3wG1MWhFx54ySFJ00CS1MVjR4UiBVsOWGIjJ5Av+HamnguqEshxbF9mvy+BX0KqzdLVzwFkoLBs8QeOID56HA==", "license": "MIT" }, "node_modules/@cspell/dict-google": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.4.tgz", - "integrity": "sha512-JThUT9eiguCja1mHHLwYESgxkhk17Gv7P3b1S7ZJzXw86QyVHPrbpVoMpozHk0C9o+Ym764B7gZGKmw9uMGduQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.8.tgz", + "integrity": "sha512-BnMHgcEeaLyloPmBs8phCqprI+4r2Jb8rni011A8hE+7FNk7FmLE3kiwxLFrcZnnb7eqM0agW4zUaNoB0P+z8A==", "license": "MIT" }, "node_modules/@cspell/dict-haskell": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.4.tgz", - "integrity": "sha512-EwQsedEEnND/vY6tqRfg9y7tsnZdxNqOxLXSXTsFA6JRhUlr8Qs88iUUAfsUzWc4nNmmzQH2UbtT25ooG9x4nA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.5.tgz", + "integrity": "sha512-s4BG/4tlj2pPM9Ha7IZYMhUujXDnI0Eq1+38UTTCpatYLbQqDwRFf2KNPLRqkroU+a44yTUAe0rkkKbwy4yRtQ==", "license": "MIT" }, "node_modules/@cspell/dict-html": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.10.tgz", - "integrity": "sha512-I9uRAcdtHbh0wEtYZlgF0TTcgH0xaw1B54G2CW+tx4vHUwlde/+JBOfIzird4+WcMv4smZOfw+qHf7puFUbI5g==", + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.11.tgz", + "integrity": "sha512-QR3b/PB972SRQ2xICR1Nw/M44IJ6rjypwzA4jn+GH8ydjAX9acFNfc+hLZVyNe0FqsE90Gw3evLCOIF0vy1vQw==", "license": "MIT" }, "node_modules/@cspell/dict-html-symbol-entities": { @@ -310,21 +316,27 @@ "license": "MIT" }, "node_modules/@cspell/dict-java": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.10.tgz", - "integrity": "sha512-pVNcOnmoGiNL8GSVq4WbX/Vs2FGS0Nej+1aEeGuUY9CU14X8yAVCG+oih5ZoLt1jaR8YfR8byUF8wdp4qG4XIw==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.11.tgz", + "integrity": "sha512-T4t/1JqeH33Raa/QK/eQe26FE17eUCtWu+JsYcTLkQTci2dk1DfcIKo8YVHvZXBnuM43ATns9Xs0s+AlqDeH7w==", "license": "MIT" }, "node_modules/@cspell/dict-julia": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.0.4.tgz", - "integrity": "sha512-bFVgNX35MD3kZRbXbJVzdnN7OuEqmQXGpdOi9jzB40TSgBTlJWA4nxeAKV4CPCZxNRUGnLH0p05T/AD7Aom9/w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.0.tgz", + "integrity": "sha512-CPUiesiXwy3HRoBR3joUseTZ9giFPCydSKu2rkh6I2nVjXnl5vFHzOMLXpbF4HQ1tH2CNfnDbUndxD+I+7eL9w==", "license": "MIT" }, "node_modules/@cspell/dict-k8s": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.9.tgz", - "integrity": "sha512-Q7GELSQIzo+BERl2ya/nBEnZeQC+zJP19SN1pI6gqDYraM51uYJacbbcWLYYO2Y+5joDjNt/sd/lJtLaQwoSlA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.10.tgz", + "integrity": "sha512-313haTrX9prep1yWO7N6Xw4D6tvUJ0Xsx+YhCP+5YrrcIKoEw5Rtlg8R4PPzLqe6zibw6aJ+Eqq+y76Vx5BZkw==", + "license": "MIT" + }, + "node_modules/@cspell/dict-kotlin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.0.tgz", + "integrity": "sha512-vySaVw6atY7LdwvstQowSbdxjXG6jDhjkWVWSjg1XsUckyzH1JRHXe9VahZz1i7dpoFEUOWQrhIe5B9482UyJQ==", "license": "MIT" }, "node_modules/@cspell/dict-latex": { @@ -334,183 +346,190 @@ "license": "MIT" }, "node_modules/@cspell/dict-lorem-ipsum": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.3.tgz", - "integrity": "sha512-WFpDi/PDYHXft6p0eCXuYnn7mzMEQLVeqpO+wHSUd+kz5ADusZ4cpslAA4wUZJstF1/1kMCQCZM6HLZic9bT8A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.4.tgz", + "integrity": "sha512-+4f7vtY4dp2b9N5fn0za/UR0kwFq2zDtA62JCbWHbpjvO9wukkbl4rZg4YudHbBgkl73HRnXFgCiwNhdIA1JPw==", "license": "MIT" }, "node_modules/@cspell/dict-lua": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.6.tgz", - "integrity": "sha512-Jwvh1jmAd9b+SP9e1GkS2ACbqKKRo9E1f9GdjF/ijmooZuHU0hPyqvnhZzUAxO1egbnNjxS/J2T6iUtjAUK2KQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.7.tgz", + "integrity": "sha512-Wbr7YSQw+cLHhTYTKV6cAljgMgcY+EUAxVIZW3ljKswEe4OLxnVJ7lPqZF5JKjlXdgCjbPSimsHqyAbC5pQN/Q==", "license": "MIT" }, "node_modules/@cspell/dict-makefile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.3.tgz", - "integrity": "sha512-R3U0DSpvTs6qdqfyBATnePj9Q/pypkje0Nj26mQJ8TOBQutCRAJbr2ZFAeDjgRx5EAJU/+8txiyVF97fbVRViw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.4.tgz", + "integrity": "sha512-E4hG/c0ekPqUBvlkrVvzSoAA+SsDA9bLi4xSV3AXHTVru7Y2bVVGMPtpfF+fI3zTkww/jwinprcU1LSohI3ylw==", "license": "MIT" }, "node_modules/@cspell/dict-markdown": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.7.tgz", - "integrity": "sha512-F9SGsSOokFn976DV4u/1eL4FtKQDSgJHSZ3+haPRU5ki6OEqojxKa8hhj4AUrtNFpmBaJx/WJ4YaEzWqG7hgqg==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.10.tgz", + "integrity": "sha512-vtVa6L/84F9sTjclTYDkWJF/Vx2c5xzxBKkQp+CEFlxOF2SYgm+RSoEvAvg5vj4N5kuqR4350ZlY3zl2eA3MXw==", "license": "MIT", "peerDependencies": { - "@cspell/dict-css": "^4.0.16", - "@cspell/dict-html": "^4.0.10", + "@cspell/dict-css": "^4.0.17", + "@cspell/dict-html": "^4.0.11", "@cspell/dict-html-symbol-entities": "^4.0.3", - "@cspell/dict-typescript": "^3.1.11" + "@cspell/dict-typescript": "^3.2.1" } }, "node_modules/@cspell/dict-monkeyc": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.9.tgz", - "integrity": "sha512-Jvf6g5xlB4+za3ThvenYKREXTEgzx5gMUSzrAxIiPleVG4hmRb/GBSoSjtkGaibN3XxGx5x809gSTYCA/IHCpA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.10.tgz", + "integrity": "sha512-7RTGyKsTIIVqzbvOtAu6Z/lwwxjGRtY5RkKPlXKHEoEAgIXwfDxb5EkVwzGQwQr8hF/D3HrdYbRT8MFBfsueZw==", "license": "MIT" }, "node_modules/@cspell/dict-node": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.5.tgz", - "integrity": "sha512-7NbCS2E8ZZRZwlLrh2sA0vAk9n1kcTUiRp/Nia8YvKaItGXLfxYqD2rMQ3HpB1kEutal6hQLVic3N2Yi1X7AaA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.7.tgz", + "integrity": "sha512-ZaPpBsHGQCqUyFPKLyCNUH2qzolDRm1/901IO8e7btk7bEDF56DN82VD43gPvD4HWz3yLs/WkcLa01KYAJpnOw==", "license": "MIT" }, "node_modules/@cspell/dict-npm": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.1.14.tgz", - "integrity": "sha512-7VV/rrRlxOwy5j0bpw6/Uci+nx/rwSgx45FJdeKq++nHsBx/nEXMFNODknm4Mi6i7t7uOVHExpifrR6w6xTWww==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.4.tgz", + "integrity": "sha512-/hK5ii9OzSOQkmTjkzJlEYWz+PBnz2hRq5Xu7d4aDURaynO9xMAcK31JJlKNQulBkVbQHxFZLUrzjdzdAr/Opw==", "license": "MIT" }, "node_modules/@cspell/dict-php": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.13.tgz", - "integrity": "sha512-P6sREMZkhElzz/HhXAjahnICYIqB/HSGp1EhZh+Y6IhvC15AzgtDP8B8VYCIsQof6rPF1SQrFwunxOv8H1e2eg==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.14.tgz", + "integrity": "sha512-7zur8pyncYZglxNmqsRycOZ6inpDoVd4yFfz1pQRe5xaRWMiK3Km4n0/X/1YMWhh3e3Sl/fQg5Axb2hlN68t1g==", "license": "MIT" }, "node_modules/@cspell/dict-powershell": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.13.tgz", - "integrity": "sha512-0qdj0XZIPmb77nRTynKidRJKTU0Fl+10jyLbAhFTuBWKMypVY06EaYFnwhsgsws/7nNX8MTEQuewbl9bWFAbsg==", + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.14.tgz", + "integrity": "sha512-ktjjvtkIUIYmj/SoGBYbr3/+CsRGNXGpvVANrY0wlm/IoGlGywhoTUDYN0IsGwI2b8Vktx3DZmQkfb3Wo38jBA==", "license": "MIT" }, "node_modules/@cspell/dict-public-licenses": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.11.tgz", - "integrity": "sha512-rR5KjRUSnVKdfs5G+gJ4oIvQvm8+NJ6cHWY2N+GE69/FSGWDOPHxulCzeGnQU/c6WWZMSimG9o49i9r//lUQyA==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.13.tgz", + "integrity": "sha512-1Wdp/XH1ieim7CadXYE7YLnUlW0pULEjVl9WEeziZw3EKCAw8ZI8Ih44m4bEa5VNBLnuP5TfqC4iDautAleQzQ==", "license": "MIT" }, "node_modules/@cspell/dict-python": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.12.tgz", - "integrity": "sha512-U25eOFu+RE0aEcF2AsxZmq3Lic7y9zspJ9SzjrC0mfJz+yr3YmSCw4E0blMD3mZoNcf7H/vMshuKIY5AY36U+Q==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.18.tgz", + "integrity": "sha512-hYczHVqZBsck7DzO5LumBLJM119a3F17aj8a7lApnPIS7cmEwnPc2eACNscAHDk7qAo2127oI7axUoFMe9/g1g==", "license": "MIT", "dependencies": { - "@cspell/dict-data-science": "^2.0.5" + "@cspell/dict-data-science": "^2.0.8" } }, "node_modules/@cspell/dict-r": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.0.4.tgz", - "integrity": "sha512-cBpRsE/U0d9BRhiNRMLMH1PpWgw+N+1A2jumgt1if9nBGmQw4MUpg2u9I0xlFVhstTIdzXiLXMxP45cABuiUeQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.0.tgz", + "integrity": "sha512-k2512wgGG0lTpTYH9w5Wwco+lAMf3Vz7mhqV8+OnalIE7muA0RSuD9tWBjiqLcX8zPvEJr4LdgxVju8Gk3OKyA==", "license": "MIT" }, "node_modules/@cspell/dict-ruby": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.7.tgz", - "integrity": "sha512-4/d0hcoPzi5Alk0FmcyqlzFW9lQnZh9j07MJzPcyVO62nYJJAGKaPZL2o4qHeCS/od/ctJC5AHRdoUm0ktsw6Q==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.8.tgz", + "integrity": "sha512-ixuTneU0aH1cPQRbWJvtvOntMFfeQR2KxT8LuAv5jBKqQWIHSxzGlp+zX3SVyoeR0kOWiu64/O5Yn836A5yMcQ==", "license": "MIT" }, "node_modules/@cspell/dict-rust": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.10.tgz", - "integrity": "sha512-6o5C8566VGTTctgcwfF3Iy7314W0oMlFFSQOadQ0OEdJ9Z9ERX/PDimrzP3LGuOrvhtEFoK8pj+BLnunNwRNrw==", + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.11.tgz", + "integrity": "sha512-OGWDEEzm8HlkSmtD8fV3pEcO2XBpzG2XYjgMCJCRwb2gRKvR+XIm6Dlhs04N/K2kU+iH8bvrqNpM8fS/BFl0uw==", "license": "MIT" }, "node_modules/@cspell/dict-scala": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.6.tgz", - "integrity": "sha512-tl0YWAfjUVb4LyyE4JIMVE8DlLzb1ecHRmIWc4eT6nkyDqQgHKzdHsnusxFEFMVLIQomgSg0Zz6hJ5S1E4W4ww==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.7.tgz", + "integrity": "sha512-yatpSDW/GwulzO3t7hB5peoWwzo+Y3qTc0pO24Jf6f88jsEeKmDeKkfgPbYuCgbE4jisGR4vs4+jfQZDIYmXPA==", + "license": "MIT" + }, + "node_modules/@cspell/dict-shell": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.0.tgz", + "integrity": "sha512-D/xHXX7T37BJxNRf5JJHsvziFDvh23IF/KvkZXNSh8VqcRdod3BAz9VGHZf6VDqcZXr1VRqIYR3mQ8DSvs3AVQ==", "license": "MIT" }, "node_modules/@cspell/dict-software-terms": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-4.1.17.tgz", - "integrity": "sha512-QORIk1R5DV8oOQ+oAlUWE7UomaJwUucqu2srrc2+PmkoI6R1fJwwg2uHCPBWlIb4PGDNEdXLv9BAD13H+0wytQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.0.10.tgz", + "integrity": "sha512-2nTcVKTYJKU5GzeviXGPtRRC9d23MtfpD4PM4pLSzl29/5nx5MxOUHkzPuJdyaw9mXIz8Rm9IlGeVAvQoTI8aw==", "license": "MIT" }, "node_modules/@cspell/dict-sql": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.1.8.tgz", - "integrity": "sha512-dJRE4JV1qmXTbbGm6WIcg1knmR6K5RXnQxF4XHs5HA3LAjc/zf77F95i5LC+guOGppVF6Hdl66S2UyxT+SAF3A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.0.tgz", + "integrity": "sha512-MUop+d1AHSzXpBvQgQkCiok8Ejzb+nrzyG16E8TvKL2MQeDwnIvMe3bv90eukP6E1HWb+V/MA/4pnq0pcJWKqQ==", "license": "MIT" }, "node_modules/@cspell/dict-svelte": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.5.tgz", - "integrity": "sha512-sseHlcXOqWE4Ner9sg8KsjxwSJ2yssoJNqFHR9liWVbDV+m7kBiUtn2EB690TihzVsEmDr/0Yxrbb5Bniz70mA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.6.tgz", + "integrity": "sha512-8LAJHSBdwHCoKCSy72PXXzz7ulGROD0rP1CQ0StOqXOOlTUeSFaJJlxNYjlONgd2c62XBQiN2wgLhtPN+1Zv7Q==", "license": "MIT" }, "node_modules/@cspell/dict-swift": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.4.tgz", - "integrity": "sha512-CsFF0IFAbRtYNg0yZcdaYbADF5F3DsM8C4wHnZefQy8YcHP/qjAF/GdGfBFBLx+XSthYuBlo2b2XQVdz3cJZBw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.5.tgz", + "integrity": "sha512-3lGzDCwUmnrfckv3Q4eVSW3sK3cHqqHlPprFJZD4nAqt23ot7fic5ALR7J4joHpvDz36nHX34TgcbZNNZOC/JA==", "license": "MIT" }, "node_modules/@cspell/dict-terraform": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.0.6.tgz", - "integrity": "sha512-Sqm5vGbXuI9hCFcr4w6xWf4Y25J9SdleE/IqfM6RySPnk8lISEmVdax4k6+Kinv9qaxyvnIbUUN4WFLWcBPQAg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.1.tgz", + "integrity": "sha512-07KFDwCU7EnKl4hOZLsLKlj6Zceq/IsQ3LRWUyIjvGFfZHdoGtFdCp3ZPVgnFaAcd/DKv+WVkrOzUBSYqHopQQ==", "license": "MIT" }, "node_modules/@cspell/dict-typescript": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.1.11.tgz", - "integrity": "sha512-FwvK5sKbwrVpdw0e9+1lVTl8FPoHYvfHRuQRQz2Ql5XkC0gwPPkpoyD1zYImjIyZRoYXk3yp9j8ss4iz7A7zoQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.1.tgz", + "integrity": "sha512-jdnKg4rBl75GUBTsUD6nTJl7FGvaIt5wWcWP7TZSC3rV1LfkwvbUiY3PiGpfJlAIdnLYSeFWIpYU9gyVgz206w==", "license": "MIT" }, "node_modules/@cspell/dict-vue": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.3.tgz", - "integrity": "sha512-akmYbrgAGumqk1xXALtDJcEcOMYBYMnkjpmGzH13Ozhq1mkPF4VgllFQlm1xYde+BUKNnzMgPEzxrL2qZllgYA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.4.tgz", + "integrity": "sha512-0dPtI0lwHcAgSiQFx8CzvqjdoXROcH+1LyqgROCpBgppommWpVhbQ0eubnKotFEXgpUCONVkeZJ6Ql8NbTEu+w==", "license": "MIT" }, "node_modules/@cspell/dynamic-import": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.16.1.tgz", - "integrity": "sha512-mEfdeS1kFKpJoDsQ8wW6PxO3+ncYuZCWCASR0trbzZDduzO2RcogMUgzP99obHtYbgXadw94qcQWXB8OYTPSwg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.0.2.tgz", + "integrity": "sha512-KhcoNUj6Ij2P8fbRC7QOn3jzbTZFxoQpFGanGU9f+4DfZBH86PCADyKYH+ZpJPlYgrI+Jh4wKzF5y5YKKNrdrw==", "license": "MIT", "dependencies": { + "@cspell/url": "9.0.2", "import-meta-resolve": "^4.1.0" }, "engines": { - "node": ">=18.0" + "node": ">=20" } }, "node_modules/@cspell/filetypes": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-8.16.1.tgz", - "integrity": "sha512-zpbNg3n26muR1jdMbylw5YsaVGyS9LU5Lfy20gU7RygAk6kFyx3Yz4C84EihBGQHy2gVEsEeyCCxk+R8RXuPZA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.0.2.tgz", + "integrity": "sha512-8KEIgptldoZT3pM+yhYV8nXq5T9Sz0YvZIqwDGEqKJ6j447K+I91QWS7RQDrvHkElMi/2g/h08Efg0RIT+QEaQ==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/strong-weak-map": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.16.1.tgz", - "integrity": "sha512-jJQS05wg2iUkLKnPR8NEq3LqvqHWKnvUDFoPwaJzYw6ol/O4yi/lv+Me9+XCPrgjpnAz+8APhWkhrR/O71R1Bw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.0.2.tgz", + "integrity": "sha512-SHTPUcu2e6aYxI5sr1L/9pzz68CArV6WzMvAio//5LbtKI6NtDp/7tARBwLi1G3A3C0289zDHbDKm3wc1lRNhQ==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@cspell/url": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-8.16.1.tgz", - "integrity": "sha512-kGlr7Wdo4xJpXKal/Gqo3Ll5Is7ptlIlLZOB/hzR6R53Fw4N6SdipTDIeHHqC15p2AXTEG6TSNdhk9dA50LY6w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.0.2.tgz", + "integrity": "sha512-KwCDL0ejgwVSZB8KTp8FhDe42UOaebTVIMi3O5GcYHi9Cut8B5QU4tbQOFGXP6E4pjimeO9yIkr9Z34kTljj/g==", "license": "MIT", "engines": { - "node": ">=18.0" + "node": ">=20" } }, "node_modules/array-timsort": { @@ -519,18 +538,6 @@ "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", "license": "MIT" }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -541,9 +548,9 @@ } }, "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -584,12 +591,12 @@ } }, "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/comment-json": { @@ -615,177 +622,175 @@ "license": "MIT" }, "node_modules/cspell": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.16.1.tgz", - "integrity": "sha512-ILuCjnY3JPY2oO62PodTQD6n3DGTKTwB+IU1tE9EC6EP2Xw6z3Ir+hO2DO6QlRUmZlGrkGMek5U06nNmztt4eA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.0.2.tgz", + "integrity": "sha512-VwPNTTivvv/NyovXUMcTYc7BaOgun7k8FhRWaVKxZPEsl/9r9WTLmQ1dNbHRq56LajH2b7wKGQYuRsfov3UWTg==", "license": "MIT", "dependencies": { - "@cspell/cspell-json-reporter": "8.16.1", - "@cspell/cspell-pipe": "8.16.1", - "@cspell/cspell-types": "8.16.1", - "@cspell/dynamic-import": "8.16.1", - "@cspell/url": "8.16.1", - "chalk": "^5.3.0", + "@cspell/cspell-json-reporter": "9.0.2", + "@cspell/cspell-pipe": "9.0.2", + "@cspell/cspell-types": "9.0.2", + "@cspell/dynamic-import": "9.0.2", + "@cspell/url": "9.0.2", + "chalk": "^5.4.1", "chalk-template": "^1.1.0", - "commander": "^12.1.0", - "cspell-dictionary": "8.16.1", - "cspell-gitignore": "8.16.1", - "cspell-glob": "8.16.1", - "cspell-io": "8.16.1", - "cspell-lib": "8.16.1", + "commander": "^14.0.0", + "cspell-dictionary": "9.0.2", + "cspell-gitignore": "9.0.2", + "cspell-glob": "9.0.2", + "cspell-io": "9.0.2", + "cspell-lib": "9.0.2", "fast-json-stable-stringify": "^2.1.0", "file-entry-cache": "^9.1.0", - "get-stdin": "^9.0.0", - "semver": "^7.6.3", - "tinyglobby": "^0.2.10" + "semver": "^7.7.2", + "tinyglobby": "^0.2.13" }, "bin": { "cspell": "bin.mjs", "cspell-esm": "bin.mjs" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" } }, "node_modules/cspell-config-lib": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.16.1.tgz", - "integrity": "sha512-ohbSi9sI14rMdFc2g17ogObGGkd/x6zUVOzCH1nEOefC9yJYYfsvaMHqdhk0rOjvmF95j5OK4dm5oid+DKQcpw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.0.2.tgz", + "integrity": "sha512-8rCmGUEzlytnNeAazvbBdLeUoN18Cct8k6KLePiUS0GglYomSAvcPWsamSk9jeh947m0cu2dhjZPnKQlp11XBA==", "license": "MIT", "dependencies": { - "@cspell/cspell-types": "8.16.1", + "@cspell/cspell-types": "9.0.2", "comment-json": "^4.2.5", - "yaml": "^2.6.1" + "yaml": "^2.8.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cspell-dictionary": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.16.1.tgz", - "integrity": "sha512-NL/vwf5SjtkWWaEUh+0dogKdEU4UuepJaNh36FX8W1CFtQXj7yEs45x4K7/Fp+pn/4AT7Qe7WpSSWi9z5GcqKg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.0.2.tgz", + "integrity": "sha512-u1jLnqu+2IJiGKdUP9LF1/vseOrCh6hUACHZQ8JsCbHC2KU/DL68s4IgS5jDyK5lBcwPOWzQOiTuXQSEardpFQ==", "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "8.16.1", - "@cspell/cspell-types": "8.16.1", - "cspell-trie-lib": "8.16.1", - "fast-equals": "^5.0.1" + "@cspell/cspell-pipe": "9.0.2", + "@cspell/cspell-types": "9.0.2", + "cspell-trie-lib": "9.0.2", + "fast-equals": "^5.2.2" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cspell-gitignore": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.16.1.tgz", - "integrity": "sha512-Gg8qvFc8wr1D7TvB+GSfT1jyrUoUmPiG3WdOnQnxOSYKJesOiVvNxLv7YXRFkcUKG1VU6XDUkpb/uzKh3k2rKw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.0.2.tgz", + "integrity": "sha512-2CXpUYa+mf1I0oMH/V0qzT0zP95IqYzaS9BfEB7AcSmjrvuIgmiGLztUNrG5mMMBAlHk7sfI8gAEMMvr/Q7sTQ==", "license": "MIT", "dependencies": { - "@cspell/url": "8.16.1", - "cspell-glob": "8.16.1", - "cspell-io": "8.16.1", - "find-up-simple": "^1.0.0" + "@cspell/url": "9.0.2", + "cspell-glob": "9.0.2", + "cspell-io": "9.0.2" }, "bin": { "cspell-gitignore": "bin.mjs" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cspell-glob": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.16.1.tgz", - "integrity": "sha512-EukaXFaUrgrY9G4bB2PguzpkAoOq6ai9acLl6gWD+6DgVEwkLqPmCWjsFJA0MaqVp9QvPsIfCy4KCnx35csG/g==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.0.2.tgz", + "integrity": "sha512-trTskAU7tw9RpCb+/uPM4zWByZEavHh3SIrjz7Du/ritjZi85O80HItNw5O3ext4zSPfNNLL3kBT7fLLphFHrw==", "license": "MIT", "dependencies": { - "@cspell/url": "8.16.1", - "micromatch": "^4.0.8" + "@cspell/url": "9.0.2", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cspell-grammar": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.16.1.tgz", - "integrity": "sha512-7IRYa0O1xfK2HVbhGSpOPPt5HlP2ZHRHtdLU2iOvMSCkh0cSPERu++kdprvcaOf7E7koo0P+bxHSprcYbU/agg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.0.2.tgz", + "integrity": "sha512-3hrNZJYEgWSaCvH3rpFq43PX9pxdJt60+pFG3CTZAdpcI97DDsrdH3f7a6h8sNAb+pN59JnV2DtWexsAVL6vjA==", "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "8.16.1", - "@cspell/cspell-types": "8.16.1" + "@cspell/cspell-pipe": "9.0.2", + "@cspell/cspell-types": "9.0.2" }, "bin": { "cspell-grammar": "bin.mjs" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cspell-io": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.16.1.tgz", - "integrity": "sha512-25MOQfy7EhdVeoNUW/+jyb5ArDYSLbaFwVToakHtLGuYk9cW8q8MAHq1W9GzW06wXswT2sQsRvaozmIOTDIOnw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.0.2.tgz", + "integrity": "sha512-TO93FTgQjjp62nAn213885RdyOTsQwdjSHdeYaaNiaTBOBgj2jR8M8bi3+h2imGBlinlYERoVbPF9wghJEK2nw==", "license": "MIT", "dependencies": { - "@cspell/cspell-service-bus": "8.16.1", - "@cspell/url": "8.16.1" + "@cspell/cspell-service-bus": "9.0.2", + "@cspell/url": "9.0.2" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cspell-lib": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.16.1.tgz", - "integrity": "sha512-Gn1vJcyhYe78iB+9dms8rnfgDEfJgYocXapFPTOcZV3EUWKcV4wyCiHdbK3j2ElLXmPuSPg4eZSlxxk8ITD0Aw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.0.2.tgz", + "integrity": "sha512-uoPQ0f+umOGUQB/q0H+K/gWfd7xJMaPlt5rXMMTeKIPHLDRBE7lBx4mHVCmgevL+oTNSLpIE5FdqRDbr+Q+Awg==", "license": "MIT", "dependencies": { - "@cspell/cspell-bundled-dicts": "8.16.1", - "@cspell/cspell-pipe": "8.16.1", - "@cspell/cspell-resolver": "8.16.1", - "@cspell/cspell-types": "8.16.1", - "@cspell/dynamic-import": "8.16.1", - "@cspell/filetypes": "8.16.1", - "@cspell/strong-weak-map": "8.16.1", - "@cspell/url": "8.16.1", + "@cspell/cspell-bundled-dicts": "9.0.2", + "@cspell/cspell-pipe": "9.0.2", + "@cspell/cspell-resolver": "9.0.2", + "@cspell/cspell-types": "9.0.2", + "@cspell/dynamic-import": "9.0.2", + "@cspell/filetypes": "9.0.2", + "@cspell/strong-weak-map": "9.0.2", + "@cspell/url": "9.0.2", "clear-module": "^4.1.2", "comment-json": "^4.2.5", - "cspell-config-lib": "8.16.1", - "cspell-dictionary": "8.16.1", - "cspell-glob": "8.16.1", - "cspell-grammar": "8.16.1", - "cspell-io": "8.16.1", - "cspell-trie-lib": "8.16.1", + "cspell-config-lib": "9.0.2", + "cspell-dictionary": "9.0.2", + "cspell-glob": "9.0.2", + "cspell-grammar": "9.0.2", + "cspell-io": "9.0.2", + "cspell-trie-lib": "9.0.2", "env-paths": "^3.0.0", - "fast-equals": "^5.0.1", + "fast-equals": "^5.2.2", "gensequence": "^7.0.0", - "import-fresh": "^3.3.0", + "import-fresh": "^3.3.1", "resolve-from": "^5.0.0", "vscode-languageserver-textdocument": "^1.0.12", - "vscode-uri": "^3.0.8", + "vscode-uri": "^3.1.0", "xdg-basedir": "^5.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cspell-trie-lib": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.16.1.tgz", - "integrity": "sha512-T86nszsjQjyZ35dOWk7qN17Hem0cVeXJ4D1v/gIG+Y0Umo7dBW7AwmTvUy8iMFAra29cSdgRH+yk6q1qdpA+ZA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.0.2.tgz", + "integrity": "sha512-inXu6YEoJFLYnxgcXy3quCoGgSWYRye1kM4dj8kbYtNAQgUVD93hPFdmPWObwhVawsS3rQybckG3DSnmxBe9Fg==", "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "8.16.1", - "@cspell/cspell-types": "8.16.1", + "@cspell/cspell-pipe": "9.0.2", + "@cspell/cspell-types": "9.0.2", "gensequence": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/env-paths": { @@ -814,9 +819,9 @@ } }, "node_modules/fast-equals": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", - "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -828,6 +833,20 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, + "node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", @@ -840,30 +859,6 @@ "node": ">=18" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/flat-cache": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", @@ -892,18 +887,6 @@ "node": ">=18" } }, - "node_modules/get-stdin": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", - "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/global-directory": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", @@ -929,9 +912,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -984,15 +967,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -1008,19 +982,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/parent-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", @@ -1034,12 +995,12 @@ } }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -1064,9 +1025,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1076,54 +1037,19 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "license": "MIT", "dependencies": { - "fdir": "^6.4.2", + "fdir": "^6.4.4", "picomatch": "^4.0.2" }, "engines": { "node": ">=12.0.0" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "license": "MIT", - "engines": { - "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/vscode-languageserver-textdocument": { @@ -1133,9 +1059,9 @@ "license": "MIT" }, "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, "node_modules/xdg-basedir": { @@ -1151,15 +1077,15 @@ } }, "node_modules/yaml": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", - "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } } } diff --git a/tests/linters/cspell/package.json b/tests/linters/cspell/package.json index d58d3f4..77d7803 100644 --- a/tests/linters/cspell/package.json +++ b/tests/linters/cspell/package.json @@ -9,9 +9,9 @@ "license": "AGPL-3.0-or-later", "private": true, "dependencies": { - "cspell": "^8.16.1" + "cspell": "^9.0.2" }, "scripts": { - "cspell": "npx cspell lint --config .cspell.json --root ../.. '**'" + "cspell": "npx cspell lint --config .cspell.json --root ../../.. '**'" } } diff --git a/tests/linters/markdownlint/package-lock.json b/tests/linters/markdownlint/package-lock.json index 932fde8..ed54bee 100644 --- a/tests/linters/markdownlint/package-lock.json +++ b/tests/linters/markdownlint/package-lock.json @@ -1,16 +1,16 @@ { - "name": "@virelay/markdownlint-config", - "version": "0.6.1", + "name": "@corelay/markdownlint-config", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@virelay/markdownlint-config", - "version": "0.6.1", + "name": "@corelay/markdownlint-config", + "version": "0.3.0", "license": "AGPL-3.0-or-later", "dependencies": { - "markdownlint": "^0.37.4", - "markdownlint-cli2": "^0.17.2" + "markdownlint": "^0.38.0", + "markdownlint-cli2": "^0.18.1" } }, "node_modules/@nodelib/fs.scandir": { @@ -145,9 +145,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -258,17 +258,17 @@ } }, "node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" + "unicorn-magic": "^0.3.0" }, "engines": { "node": ">=18" @@ -278,9 +278,9 @@ } }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "license": "MIT", "engines": { "node": ">= 4" @@ -421,38 +421,38 @@ } }, "node_modules/markdownlint": { - "version": "0.37.4", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.37.4.tgz", - "integrity": "sha512-u00joA/syf3VhWh6/ybVFkib5Zpj2e5KB/cfCei8fkSRuums6nyisTWGqjTWIOFoFwuXoTBQQiqlB4qFKp8ncQ==", + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz", + "integrity": "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==", "license": "MIT", "dependencies": { - "markdown-it": "14.1.0", - "micromark": "4.0.1", - "micromark-core-commonmark": "2.0.2", - "micromark-extension-directive": "3.0.2", + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", "micromark-extension-gfm-autolink-literal": "2.1.0", "micromark-extension-gfm-footnote": "2.1.0", - "micromark-extension-gfm-table": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", "micromark-extension-math": "3.1.0", - "micromark-util-types": "2.0.1" + "micromark-util-types": "2.0.2" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/DavidAnson" } }, "node_modules/markdownlint-cli2": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.17.2.tgz", - "integrity": "sha512-XH06ZOi8wCrtOSSj3p8y3yJzwgzYOSa7lglNyS3fP05JPRzRGyjauBb5UvlLUSCGysMmULS1moxdRHHudV+g/Q==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.18.1.tgz", + "integrity": "sha512-/4Osri9QFGCZOCTkfA8qJF+XGjKYERSHkXzxSyS1hd3ZERJGjvsUao2h4wdnvpHp6Tu2Jh/bPHM0FE9JJza6ng==", "license": "MIT", "dependencies": { - "globby": "14.0.2", + "globby": "14.1.0", "js-yaml": "4.1.0", "jsonc-parser": "3.3.1", - "markdownlint": "0.37.4", + "markdown-it": "14.1.0", + "markdownlint": "0.38.0", "markdownlint-cli2-formatter-default": "0.0.5", "micromatch": "4.0.8" }, @@ -460,7 +460,7 @@ "markdownlint-cli2": "markdownlint-cli2-bin.mjs" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/DavidAnson" @@ -494,9 +494,9 @@ } }, "node_modules/micromark": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", - "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "funding": [ { "type": "GitHub Sponsors", @@ -529,9 +529,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", - "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "funding": [ { "type": "GitHub Sponsors", @@ -563,9 +563,9 @@ } }, "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -618,9 +618,9 @@ } }, "node_modules/micromark-extension-gfm-table": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", - "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -989,9 +989,9 @@ "license": "MIT" }, "node_modules/micromark-util-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", - "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "funding": [ { "type": "GitHub Sponsors", @@ -1043,12 +1043,12 @@ } }, "node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1159,9 +1159,9 @@ "license": "MIT" }, "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "license": "MIT", "engines": { "node": ">=18" diff --git a/tests/linters/markdownlint/package.json b/tests/linters/markdownlint/package.json index 9e8a317..819831f 100644 --- a/tests/linters/markdownlint/package.json +++ b/tests/linters/markdownlint/package.json @@ -9,8 +9,8 @@ "license": "AGPL-3.0-or-later", "private": true, "dependencies": { - "markdownlint": "^0.37.4", - "markdownlint-cli2": "^0.17.2" + "markdownlint": "^0.38.0", + "markdownlint-cli2": "^0.18.1" }, "scripts": { "markdownlint": "npx markdownlint-cli2 --config .markdownlint.yaml '../../../**/*.md' '#../../../**/node_modules' '#../../../**/.venv' '#../../../**/.tox' '#../../../**/.pytest_cache'" diff --git a/tests/unit_tests/.coveragerc b/tests/unit_tests/.coveragerc index d49bb8f..69c23c7 100644 --- a/tests/unit_tests/.coveragerc +++ b/tests/unit_tests/.coveragerc @@ -22,6 +22,9 @@ skip_covered = true # Specifies that the report should include a list of the lines that were not covered by the unit tests show_missing = true +# Excludes lines that raise a NotImplementedError from the coverage report; which we use in abstract methods +exclude_also = raise NotImplementedError + # Since the coverage data can be collected from multiple different installations of CoRelAy, the Coverage tool needs to know which files are # equivalent; this configuration section contains named lists (in our case only a single list called "source"), where two file paths are considered diff --git a/tests/unit_tests/corelay/io/test_hashing.py b/tests/unit_tests/corelay/io/test_hashing.py new file mode 100644 index 0000000..3c13300 --- /dev/null +++ b/tests/unit_tests/corelay/io/test_hashing.py @@ -0,0 +1,203 @@ +"""A module that contains unit tests for the :py:mod:`corelay.io.hashing` module.""" + +import importlib +import sys +import typing +from io import BytesIO +from types import ModuleType + +import numpy +import pytest + +import corelay.io.hashing +from corelay.io.hashing import HashPickler, Hasher, TensorPlaceholder, Tensor + + +def test_tensor_placeholder_is_not_implemented() -> None: + """Tests that the :py:class:`~corelay.io.hashing.TensorPlaceholder` class raises a :py:class:`NotImplementedError` exception when calling + :py:meth:`~corelay.io.hashing.TensorPlaceholder.numpy`. + """ + + tensor_placeholder = TensorPlaceholder() + with pytest.raises(NotImplementedError): + tensor_placeholder.numpy() + + +def test_tensor_class_is_placeholder_when_pytorch_not_installed() -> None: + """Tests that the :py:class:`~corelay.io.hashing.TensorPlaceholder` class is used as a placeholder for :py:class:`~torch.Tensor` when PyTorch is + not installed. + """ + + assert Tensor is TensorPlaceholder + + +def test_tensor_class_is_pytorch_tensor_when_pytorch_installed() -> None: + """Tests that the :py:class:`~corelay.io.hashing.Tensor` class is a :py:class:`~torch.Tensor` when PyTorch is installed.""" + + class PyTorchTensorMock: + """A mock class to simulate the PyTorch Tensor class.""" + + # Since PyTorch is not actually installed in the test environment, we need to mock the torch module + pytorch_module_mock = ModuleType('torch') + pytorch_module_mock.__dict__.update({'Tensor': PyTorchTensorMock}) + sys.modules['torch'] = pytorch_module_mock + + # Since the corelay.io.hashing module has already been imported, we need to reload it to ensure that the Tensor class is updated + importlib.reload(corelay.io.hashing) + + # Now we can test that the Tensor class is a PyTorch Tensor + assert corelay.io.hashing.Tensor is PyTorchTensorMock # type: ignore[comparison-overlap] + + # After the test, we need to ensure that the PyTorch module is no longer available and reload the corelay.io.hashing module, so that it reverts + # back to using the TensorPlaceholder class + del sys.modules['torch'] + importlib.reload(corelay.io.hashing) + + +class TestHasher: + """Contains unit tests for the :py:class:`~corelay.io.hashing.Hasher` class.""" + + @staticmethod + def test_hasher_produces_correct_hash() -> None: + """Tests that the :py:func:`~corelay.io.hashing.hasher` function produces the correct hash for a given input.""" + + # Both the test string and the expected hash were taken from the test cases in the original source code of the MetroHash Python library on + # which the Hasher class is based (the test string was selected such that it will hit each internal branch of the MetroHash algorithm and + # therefore must be at least 63 bytes long) + hasher = Hasher() + test_string = b'012345678901234567890123456789012345678901234567890123456789012' + number_of_bytes_written = hasher.write(test_string) + assert number_of_bytes_written == 63 + hash_digest = hasher.hexdigest() + assert hash_digest == 'c77ce2bfa4ed9f9b0548b2ac5074a297' + + +class TestHashPickler: + """Contains unit tests for the :py:class:`~corelay.io.hashing.HashPickler` class.""" + + @staticmethod + def test_correct_numpy_id_is_generated() -> None: + """Tests that the :py:func:`~corelay.io.hashing.HashPickler.numpy_id` function generates the correct NumPy ID for a given NumPy array.""" + + test_array = numpy.array([1.0, 2.0, 3.0]) + test_array_numpy_id = HashPickler.numpy_id(test_array) + + data_type_name, shape, mantissa, exponent = test_array_numpy_id + assert data_type_name == 'float64' + assert shape == (3,) + assert mantissa == b'\x00\x00\x00\x00\x00\x00\xe0?\x00\x00\x00\x00\x00\x00\xe0?\x00\x00\x00\x00\x00\x00\xe8?' + assert exponent == b'\x01\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00' + + @staticmethod + def test_correct_persistent_id_is_generated_for_numpy_array() -> None: + """Tests that the :py:func:`~corelay.io.hashing.HashPickler.persistent_id` function generates the correct persistent ID for a given NumPy + array. + """ + + hash_pickler = HashPickler(BytesIO()) + test_array = numpy.array([1.0, 2.0, 3.0]) + test_array_persistent_id = hash_pickler.persistent_id(test_array) + + assert test_array_persistent_id is not None + data_type_name, shape, mantissa, exponent = test_array_persistent_id + assert data_type_name == 'float64' + assert shape == (3,) + assert mantissa == b'\x00\x00\x00\x00\x00\x00\xe0?\x00\x00\x00\x00\x00\x00\xe0?\x00\x00\x00\x00\x00\x00\xe8?' + assert exponent == b'\x01\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00' + + @staticmethod + def test_correct_persistent_id_is_generated_for_pytorch_tensor() -> None: + """Tests that the :py:func:`~corelay.io.hashing.HashPickler.persistent_id` function generates the correct persistent ID for a given PyTorch + tensor. + """ + + class PyTorchTensorMock: + """A mock class to simulate the PyTorch Tensor class.""" + + def numpy(self) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: + """Converts the tensor to a NumPy array. + + Returns: + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns a NumPy array representation of the PyTorch tensor. + """ + + return numpy.array([1.0, 2.0, 3.0]) + + # Since PyTorch is not actually installed in the test environment, we need to mock the torch module + pytorch_module_mock = ModuleType('torch') + pytorch_module_mock.__dict__.update({'Tensor': PyTorchTensorMock}) + sys.modules['torch'] = pytorch_module_mock + + # Since the corelay.io.hashing module has already been imported, we need to reload it to ensure that the Tensor class is updated + importlib.reload(corelay.io.hashing) + + # Now we can test that the persistent_id function generates the correct ID for a PyTorch tensor + hash_pickler = corelay.io.hashing.HashPickler(BytesIO()) + test_tensor = PyTorchTensorMock() + test_tensor_persistent_id = hash_pickler.persistent_id(test_tensor) + + # Checks if the returned persistent ID contains the expected values + assert test_tensor_persistent_id is not None + data_type_name, shape, mantissa, exponent = test_tensor_persistent_id + assert data_type_name == 'float64' + assert shape == (3,) + assert mantissa == b'\x00\x00\x00\x00\x00\x00\xe0?\x00\x00\x00\x00\x00\x00\xe0?\x00\x00\x00\x00\x00\x00\xe8?' + assert exponent == b'\x01\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00' + + # After the test, we need to ensure that the PyTorch module is no longer available and reload the corelay.io.hashing module, so that it + # reverts back to using the TensorPlaceholder class + del sys.modules['torch'] + importlib.reload(corelay.io.hashing) + + @staticmethod + def test_correct_persistent_id_is_generated_for_string() -> None: + """Tests that the :py:func:`~corelay.io.hashing.HashPickler.persistent_id` function generates the correct persistent ID for a given string. + For anything other than a NumPy array or a PyTorch tensor, the persistent ID should be :py:obj:`None`. + """ + + hash_pickler = HashPickler(BytesIO()) + test_string = 'This is a test string.' + test_string_persistent_id = hash_pickler.persistent_id(test_string) + + assert test_string_persistent_id is None + + +def test_ext_hash_produces_correct_hash() -> None: + """Tests that the :py:func:`~corelay.io.hashing.ext_hash` function correctly uses the :py:class:`~corelay.io.hashing.HashPickler` to produce a + hash of test data. + """ + + class PyTorchTensorMock: + """A mock class to simulate the PyTorch Tensor class.""" + + def numpy(self) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: + """Converts the tensor to a NumPy array. + + Returns: + numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: Returns a NumPy array representation of the PyTorch tensor. + """ + + return numpy.array([1.0, 2.0, 3.0]) + + # Since PyTorch is not actually installed in the test environment, we need to mock the torch module + pytorch_module_mock = ModuleType('torch') + pytorch_module_mock.__dict__.update({'Tensor': PyTorchTensorMock}) + sys.modules['torch'] = pytorch_module_mock + + # Since the corelay.io.hashing module has already been imported, we need to reload it to ensure that the Tensor class is updated + importlib.reload(corelay.io.hashing) + + # Now we can test that the HashPickler can pickle and unpickle test data correctly (the hash was pre-computed, this test is merely to detect if a + # breaking change was introduced in the MetroHash or MetroHash Python libraries or in the pickle module) + test_data = { + 'string': 'This is a test string.', + 'numpy_array': numpy.array([1.0, 2.0, 3.0]), + 'pytorch_tensor': PyTorchTensorMock(), + } + test_data_hash = corelay.io.hashing.ext_hash(test_data) + assert test_data_hash == '6015b8a73a4548ea9a3d87616fc421ec' + + # After the test, we need to ensure that the PyTorch module is no longer available and reload the corelay.io.hashing module, so that it + # reverts back to using the TensorPlaceholder class + del sys.modules['torch'] + importlib.reload(corelay.io.hashing) diff --git a/tests/unit_tests/corelay/io/test_storage.py b/tests/unit_tests/corelay/io/test_storage.py index 03959e6..37bcb73 100644 --- a/tests/unit_tests/corelay/io/test_storage.py +++ b/tests/unit_tests/corelay/io/test_storage.py @@ -92,6 +92,16 @@ def test_read_tuple() -> None: data_read = io_object.read(data_in=1, meta=1) assert all((out == load).all() for out, load in zip(data_out, data_read)) + @staticmethod + def test_read_unavailable_data() -> None: + """Tests that reading data that was not written raises a :py:class:`~corelay.io.storage.NoDataSource` exception""" + + with BytesIO() as buffer, h5py.File(buffer, 'w') as hdf5_file: + group = hdf5_file.require_group('hashed') + io_object = HashedHDF5(group) + with pytest.raises(io.NoDataSource): + io_object.read(data_in=1, meta=1) + @pytest.mark.parametrize('data_storage_type', [io.HDF5Storage, io.PickleStorage]) def test_data_storage_at_functionality(data_storage_type: type[io.HDF5Storage | io.PickleStorage], tmp_path: Path) -> None: @@ -123,6 +133,166 @@ def test_data_storage_at_functionality(data_storage_type: type[io.HDF5Storage | data_storage.at(data_key='param_values').at(data_key='data') +@pytest.mark.parametrize('data_storage_type', [io.HDF5Storage, io.PickleStorage]) +def test_data_storage_io_mode(data_storage_type: type[io.HDF5Storage | io.PickleStorage], tmp_path: Path) -> None: + """Tests that the IO mode of :py:class:`~corelay.io.storage.HDF5Storage` and :py:class:`~corelay.io.storage.PickleStorage` must be 'r', 'w', or + 'a'. + + Args: + data_storage_type (type[io.HDF5Storage | io.PickleStorage]): The storage class to be tested. + tmp_path (Path): A temporary path for testing. + """ + + test_path = tmp_path / 'test.file' + data_storage: DataStorageBase = data_storage_type(test_path, mode='w') + assert data_storage + data_storage.close() + + with pytest.raises(ValueError): + data_storage = data_storage_type(test_path, mode='x') + + +@pytest.mark.parametrize('data_storage_type', [io.HDF5Storage, io.PickleStorage]) +def test_data_storage_indexer_access(data_storage_type: type[io.HDF5Storage | io.PickleStorage], tmp_path: Path) -> None: + """Tests that :py:class:`~corelay.io.storage.HDF5Storage` and :py:class:`~corelay.io.storage.PickleStorage` can be accessed using the indexer + instead of using the :py:meth:`HDF5Storage.at ` or + :meth:`PickleStorage.at ` methods. + + Args: + data_storage_type (type[io.HDF5Storage | io.PickleStorage]): The storage class to be tested. + tmp_path (Path): A temporary path for testing. + """ + + # Tests writing data using the indexer + test_path = tmp_path / 'test.file' + data_storage_writer: DataStorageBase = data_storage_type(test_path, mode='w') + data_storage_writer['data'] = {'key1': 'value1', 'key2': 'value2'} + + # Tests that writing at an index that is not a string raises a TypeError + with pytest.raises(TypeError): + data_storage_writer[123] = 'value' # type: ignore[index] + data_storage_writer.close() + + # Tests reading data using the indexer + data_storage_reader: DataStorageBase = data_storage_type(test_path, mode='r') + assert 'data' in data_storage_reader + data_read = data_storage_reader['data'] + assert isinstance(data_read, dict) + assert data_read['key1'] == 'value1' + assert data_read['key2'] == 'value2' + assert len(data_read) == 2 + + # Tests that reading from an index that is not a string raises a TypeError + with pytest.raises(TypeError): + _ = 123 in data_storage_reader # type: ignore[operator] + with pytest.raises(TypeError): + _ = data_storage_reader[123] # type: ignore[index] + + # Tests that accessing a non-existing key raises an exception + with pytest.raises(io.NoDataSource): + _ = data_storage_reader['non_existing'] + data_storage_reader.close() + + +def test_hdf5_storage_treats_tuples_like_dictionaries_with_indices_as_keys(tmp_path: Path) -> None: + """Tests that :py:class:`~corelay.io.storage.HDF5Storage` treats tuples like dictionaries with indices as keys. + + Args: + tmp_path (Path): A temporary path for testing. + """ + + test_path = tmp_path / 'test.file' + data_storage_writer: io.HDF5Storage = io.HDF5Storage(test_path, mode='w') + data_storage_writer['data'] = (numpy.array([1, 2, 3]), numpy.array([4, 5, 6]), numpy.array([7, 8, 9])) + data_storage_writer.close() + + data_storage_reader: io.HDF5Storage = io.HDF5Storage(test_path, mode='r') + assert 'data' in data_storage_reader + numpy.testing.assert_equal(data_storage_reader['data'][0], numpy.array([1, 2, 3])) + numpy.testing.assert_equal(data_storage_reader['data'][1], numpy.array([4, 5, 6])) + numpy.testing.assert_equal(data_storage_reader['data'][2], numpy.array([7, 8, 9])) + data_storage_reader.close() + + +def test_pickle_storage_can_returns_keys(tmp_path: Path) -> None: + """Tests that :py:class:`~corelay.io.storage.PickleStorage` can return the keys of the data storage container. + + Args: + tmp_path (Path): A temporary path for testing. + """ + + test_path = tmp_path / 'test.file' + with io.PickleStorage(test_path, mode='w') as data_storage_writer: + data_storage_writer['data'] = {'key1': 'value1', 'key2': 'value2'} + data_storage_writer['parameter_values'] = {'key3': 3, 'key4': 4} + data_storage_writer['new_entry'] = {'key5': 5.0, 'key6': 6.0} + data_storage_writer['test/foo'] = {'key7': True, 'key8': False} + data_storage_writer['test/bar'] = {'key9': numpy.array([1, 2, 3]), 'key10': numpy.array([4, 5, 6])} + + keys = data_storage_writer.keys() + assert len(keys) == 5 + assert 'data' in keys + assert 'parameter_values' in keys + assert 'new_entry' in keys + assert 'test/foo' in keys + assert 'test/bar' in keys + + +def test_hdf5_storage_can_returns_keys(tmp_path: Path) -> None: + """Tests that :py:class:`~corelay.io.storage.HDF5Storage` can return the keys of the data storage container. + + Args: + tmp_path (Path): A temporary path for testing. + """ + + test_path = tmp_path / 'test.file' + with io.HDF5Storage(test_path, mode='w') as data_storage_writer: + data_storage_writer['data'] = {'key1': 'value1', 'key2': 'value2'} + data_storage_writer['parameter_values'] = {'key3': 3, 'key4': 4} + data_storage_writer['new_entry'] = {'key5': 5.0, 'key6': 6.0} + data_storage_writer['test/foo'] = {'key7': True, 'key8': False} + data_storage_writer['test/bar'] = {'key9': numpy.array([1, 2, 3]), 'key10': numpy.array([4, 5, 6])} + + keys = data_storage_writer.keys() + assert len(keys) == 4 + assert 'data' in keys + assert 'parameter_values' in keys + assert 'new_entry' in keys + assert 'test' in keys + + +def test_hdf5_storage_unpacks_nested_groups_to_ordered_dictionary(tmp_path: Path) -> None: + """Tests that :py:class:`~corelay.io.storage.HDF5Storage` unpacks nested groups to an :py:class:`~collections.OrderedDict` when writing data. + + Args: + tmp_path (Path): A temporary path for testing. + """ + + test_path = tmp_path / 'test.file' + with io.HDF5Storage(test_path, mode='w') as data_storage_writer: + data_storage_writer['test/foo'] = {'key1': True, 'key2': False} + data_storage_writer['test/bar'] = {'key3': numpy.array([1, 2, 3]), 'key4': numpy.array([4, 5, 6])} + data_storage_writer['test/baz'] = 'This is a string value' + + data_dictionary = data_storage_writer.at(data_key='test').read() + assert isinstance(data_dictionary, typing.OrderedDict) + + assert 'foo' in data_dictionary + assert 'key1' in data_dictionary['foo'] + numpy.testing.assert_equal(data_dictionary['foo']['key1'], numpy.True_) + assert 'key2' in data_dictionary['foo'] + numpy.testing.assert_equal(data_dictionary['foo']['key2'], numpy.False_) + + assert 'bar' in data_dictionary + assert 'key3' in data_dictionary['bar'] + numpy.testing.assert_equal(data_dictionary['bar']['key3'], numpy.array([1, 2, 3])) + assert 'key4' in data_dictionary['bar'] + numpy.testing.assert_equal(data_dictionary['bar']['key4'], numpy.array([4, 5, 6])) + + assert 'baz' in data_dictionary + assert data_dictionary['baz'] == 'This is a string value' + + @pytest.mark.parametrize('data_storage_type', [io.HDF5Storage, io.PickleStorage]) def test_data_storage( data_storage_type: type[io.HDF5Storage | io.PickleStorage], @@ -143,34 +313,50 @@ def test_data_storage( # Tests writing data test_path = tmp_path / 'test.file' data_storage_writer: DataStorageBase = data_storage_type(test_path, mode='w') + assert data_storage_writer data_storage_writer.at(data_key='data').write(data) data_storage_writer.close() + # Tests writing data without using the at method to set the data key + data_storage_writer = data_storage_type(test_path, mode='a', data_key='new_data') + data_storage_writer.write(data) + data_storage_writer.close() + # Tests reading data data_storage_reader: DataStorageBase = data_storage_type(test_path, mode='r') + keys = data_storage_reader.keys() + assert len(keys) == 2 + assert 'data' in keys + assert 'new_data' in keys returned_data = data_storage_reader.at(data_key='data').read() numpy.testing.assert_equal(returned_data, data) data_storage_reader.close() + # Tests reading data without using the at method to get the data + data_storage_reader = data_storage_type(test_path, mode='r', data_key='new_data') + returned_data = data_storage_reader.read(data) + numpy.testing.assert_equal(returned_data, data) + data_storage_reader.close() + # Tests writing data with a context manager with data_storage_type(test_path, mode='a') as data_storage_writer: - data_storage_writer.at(data_key='param_values').write(parameter_values) + data_storage_writer.at(data_key='parameter_values').write(parameter_values) # Tests reading data with a context manager with data_storage_type(test_path, mode='r') as data_storage_reader: - assert 'param_values' in data_storage_reader + assert 'parameter_values' in data_storage_reader assert 'data' in data_storage_reader - assert data_storage_reader.at(data_key='param_values').exists() + assert data_storage_reader.at(data_key='parameter_values').exists() assert data_storage_reader.at(data_key='data').exists() assert not data_storage_reader.at(data_key='non_existing').exists() - first_returned_parameter_values = data_storage_reader['param_values'] - second_returned_parameter_values = data_storage_reader.at(data_key='param_values').read() + first_returned_parameter_values = data_storage_reader['parameter_values'] + second_returned_parameter_values = data_storage_reader.at(data_key='parameter_values').read() with pytest.raises(io.NoDataSource): _ = data_storage_reader['non_existing'] + numpy.testing.assert_equal(first_returned_parameter_values, parameter_values) + numpy.testing.assert_equal(second_returned_parameter_values, parameter_values) - numpy.testing.assert_equal(first_returned_parameter_values, parameter_values) - numpy.testing.assert_equal(second_returned_parameter_values, parameter_values) - + # Tests that a data storage container opened for writing can also be read from with data_storage_type(test_path, mode='a') as data_storage_reader_writer: data_storage_reader_writer.at(data_key='new_entry/data').write(data) assert data_storage_reader_writer.at(data_key='new_entry/data').exists() @@ -186,8 +372,13 @@ def test_no_storage(data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): Random test data with shape (10, 2). """ - data_storage = io.NoStorage() - with pytest.raises(io.NoDataSource): - data_storage.read() - with pytest.raises(io.NoDataTarget): - data_storage.write(data) + with io.NoStorage() as data_storage: + with pytest.raises(io.NoDataSource): + data_storage.read() + with pytest.raises(io.NoDataTarget): + data_storage.write(data) + with pytest.raises(io.NoDataSource): + data_storage.exists() + with pytest.raises(io.NoDataSource): + data_storage.keys() + assert not data_storage diff --git a/tests/unit_tests/corelay/pipeline/test_base.py b/tests/unit_tests/corelay/pipeline/test_base.py index 26779a5..4395d2a 100644 --- a/tests/unit_tests/corelay/pipeline/test_base.py +++ b/tests/unit_tests/corelay/pipeline/test_base.py @@ -23,8 +23,8 @@ def get_processor_type_fixture() -> type[Processor]: class MyProcessor(Processor): """A custom :py:class:`~corelay.processor.base.Processor` type.""" - param_1: Annotated[typing.Any, Param(typing.Any)] - param_2: Annotated[typing.Any, Param(typing.Any)] + param_1: Annotated[str, Param(str, 'default_value')] + param_2: Annotated[int, Param(int, 42)] def function(self, data: typing.Any) -> typing.Any: """Multiplies the input data by 2. @@ -83,13 +83,13 @@ class TestTask: """Contains unit tests for the :py:class:`~corelay.pipeline.base.Task` class.""" @staticmethod - def test_instantiation_default() -> None: + def test_init() -> None: """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` without any arguments succeeds.""" Task() @staticmethod - def test_instantiation_arguments(processor_type: type[Processor]) -> None: + def test_init_consistent_args(processor_type: type[Processor]) -> None: """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with correct arguments succeeds. Args: @@ -100,7 +100,7 @@ def test_instantiation_arguments(processor_type: type[Processor]) -> None: Task(proc_type=processor_type, default=processor_type(), is_output=True) @staticmethod - def test_proc_type_no_proc() -> None: + def test_init_with_invalid_proc_type() -> None: """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with a ``proc_type`` that is not a sub-class of :py:class:`~corelay.processor.base.Processor` raises a :py:class:`TypeError`. """ @@ -109,7 +109,7 @@ def test_proc_type_no_proc() -> None: Task(proc_type=FunctionType, default=lambda x: x) # type: ignore[arg-type] @staticmethod - def test_default_no_proc() -> None: + def test_init_without_proc_type() -> None: """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with a default value that is not of type :py:class:`~corelay.processor.base.Processor` fails. """ @@ -118,7 +118,7 @@ def test_default_no_proc() -> None: Task(default='bla') # type: ignore[arg-type] @staticmethod - def test_proc_type_default_type_mismatch(processor_type: type[Processor]) -> None: + def test_init_inconsistent_args(processor_type: type[Processor]) -> None: """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` with a default value that is not of type ``proc_type`` raises a :py:class:`TypeError`. @@ -130,6 +130,11 @@ def test_proc_type_default_type_mismatch(processor_type: type[Processor]) -> Non with pytest.raises(TypeError): Task(proc_type=processor_type, default=lambda x: x) + def test_init_without_default(self) -> None: + """Tests that the instantiation of a :py:class:`~corelay.pipeline.base.Task` without a default value succeeds.""" + + Task(proc_type=Processor, default=None) + @staticmethod def test_default_function_identity() -> None: """Tests that the default function of a :py:class:`~corelay.processor.base.FunctionProcessor` is the identity function.""" @@ -153,6 +158,163 @@ def test_assigned_default(processor_type: type[Processor]) -> None: assert task.default is not None assert task.default(5) == 10 # pylint: disable=not-callable + @staticmethod + def test_class_call() -> None: + """Tests that calling a :py:class:`~corelay.pipeline.base.Task` yields the :py:class:`~corelay.pipeline.base.TaskPlug` associated with the + :py:class:`~corelay.pipeline.base.Task`. + """ + + task = Task(proc_type=Processor, default=FunctionProcessor()) + assert task is task().slot + + @staticmethod + def test_class_call_obj() -> None: + """Tests that calling a :py:class:`~corelay.pipeline.base.Task` with an ``obj`` argument yields a :py:class:`~corelay.pipeline.base.TaskPlug` + with the :py:attr:`~corelay.plugboard.Plug.obj` property set to that value. + """ + + default_function_processor = FunctionProcessor() + task = Task(proc_type=FunctionProcessor, default=default_function_processor) + + first_function_processor = FunctionProcessor(processing_function=lambda x: x + 10) + task_plug = task(obj=first_function_processor) + assert task_plug.obj == first_function_processor + + second_function_processor = FunctionProcessor(processing_function=lambda x: x + 20) + task_plug.obj = second_function_processor + assert task_plug.obj == second_function_processor + + del task_plug.obj + assert task_plug.obj == default_function_processor + + lambda_expression = lambda x: x + 30 + task_plug.obj = lambda_expression + assert isinstance(task_plug.obj, FunctionProcessor) + assert task_plug.obj.processing_function == lambda_expression + + task_plug.obj = None + assert task_plug.obj == default_function_processor + + class CustomProcessor(Processor): + """A custom :py:class:`~corelay.processor.base.Processor` type.""" + + def function(self, data: typing.Any) -> typing.Any: + """Processes the input data by returning it unchanged. + + Args: + data (typing.Any): The input data that is to be processed. + + Returns: + typing.Any: Returns the processed data. + """ + + return data + + with pytest.raises(TypeError): + task_plug.obj = CustomProcessor() + + @staticmethod + def test_class_call_default() -> None: + """Tests that calling a :py:class:`~corelay.pipeline.base.Task` with ``default`` argument yields a :py:class:`~corelay.pipeline.base.TaskPlug` + with the :py:attr:`~corelay.pipeline.base.TaskPlug.default` property set. + """ + + default_function_processor = FunctionProcessor() + task = Task(proc_type=FunctionProcessor, default=default_function_processor) + + first_function_processor = FunctionProcessor(processing_function=lambda x: x + 10) + task_plug = task(default=first_function_processor) + assert task_plug.default == first_function_processor + + second_function_processor = FunctionProcessor(processing_function=lambda x: x + 20) + task_plug.default = second_function_processor + assert task_plug.default == second_function_processor + + del task_plug.default + assert task_plug.default == default_function_processor + + lambda_expression = lambda x: x + 30 + task_plug.default = lambda_expression + assert isinstance(task_plug.default, FunctionProcessor) + assert task_plug.default.processing_function == lambda_expression # pylint: disable=no-member + + task_plug.default = None + assert task_plug.default == default_function_processor + + class CustomProcessor(Processor): + """A custom :py:class:`~corelay.processor.base.Processor` type.""" + + def function(self, data: typing.Any) -> typing.Any: + """Processes the input data by returning it unchanged. + + Args: + data (typing.Any): The input data that is to be processed. + + Returns: + typing.Any: Returns the processed data. + """ + + return data + + with pytest.raises(TypeError): + task_plug.default = CustomProcessor() + + def test_updating_default(self) -> None: + """Tests that updating the default value of a :py:class:`~corelay.pipeline.base.Task` works as expected.""" + + default_function_processor = FunctionProcessor() + task = Task(proc_type=FunctionProcessor, default=default_function_processor) + assert task.default == default_function_processor + + updated_function_processor = FunctionProcessor(processing_function=lambda x: x + 10) + task.default = updated_function_processor + assert task.default == updated_function_processor + + del task.default + assert task.default is None + + lambda_expression = lambda x: x + 30 + task.default = lambda_expression + assert isinstance(task.default, FunctionProcessor) + assert task.default.processing_function == lambda_expression # pylint: disable=no-member + + task.default = None + assert task.default is None + + with pytest.raises(TypeError): + task.default = 'not a processor' + + class CustomProcessor(Processor): + """A custom :py:class:`~corelay.processor.base.Processor` type.""" + + def function(self, data: typing.Any) -> typing.Any: + """Processes the input data by returning it unchanged. + + Args: + data (typing.Any): The input data that is to be processed. + + Returns: + typing.Any: Returns the processed data. + """ + + return data + + with pytest.raises(TypeError): + task.default = CustomProcessor() + + @staticmethod + def test_string_representation_of_task() -> None: + """Tests that the string representation of a :py:class:`~corelay.pipeline.base.Task` instance is correct. Sphinx AutoDoc uses :py:func:`repr` + when it encounters :py:class:`typing.Annotated`, which in turn uses :py:func:`repr` to get a string representation of its metadata. This is a + reasonable thing to do, but then Intersphinx tries to resolve the resulting string as types for cross-referencing, which is not possible with + the default implementation of :py:meth:`object.__repr__`. To be able to get proper documentation, the fully-qualified name of the class needs + to be returned, because this enable Sphinx AutoDoc to reference the class in the documentation. The tilde in front is interpreted by AutoDoc + to mean that only the last part of the fully-qualified name should be displayed in the documentation. + """ + + param = Task() + assert repr(param) == '~corelay.pipeline.base.Task' + class TestPipeline: """Contains unit tests for the :py:class:`~corelay.pipeline.base.Pipeline` class.""" @@ -250,7 +412,7 @@ def test_default_param_values(pipeline_type: type[Pipeline], processor_type: typ @staticmethod def test_checkpoint_processes(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: - """Tests that collecting all processors relevant to a checkpoint succeeds. + """Tests that collecting all checkpoint processors succeeds. Args: pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. @@ -263,6 +425,27 @@ def test_checkpoint_processes(pipeline_type: type[Pipeline], processor_type: typ assert pipeline.checkpoint_processes() == collections.OrderedDict(task_2=second_processor) + @staticmethod + def test_checkpoint_processes_empty() -> None: + """Tests that collecting all checkpoint processors returns an empty dictionary if the pipeline contains no tasks.""" + + pipeline = Pipeline() + assert pipeline.checkpoint_processes() == collections.OrderedDict() + + @staticmethod + def test_checkpoint_processes_no_checkpoints() -> None: + """Tests that collecting all checkpoint processors fails if no checkpoint processors are present.""" + + class MyPipeline(Pipeline): + """A custom :py:class:`~corelay.pipeline.base.Pipeline` type.""" + + task_1: Annotated[Processor, Task()] + task_2: Annotated[Processor, Task()] + + with pytest.raises(RuntimeError): + pipeline = MyPipeline() + pipeline.checkpoint_processes() + @staticmethod def test_checkpoint_data(pipeline_type: type[Pipeline], processor_type: type[Processor]) -> None: """Tests that the checkpoint data is stored correctly. @@ -291,7 +474,29 @@ def test_from_checkpoint(pipeline_type: type[Pipeline], processor_type: type[Pro first_processor = FunctionProcessor(processing_function=lambda x: x + 5, is_checkpoint=True) second_processor = processor_type(is_checkpoint=False) pipeline = pipeline_type(task_1=first_processor, task_2=second_processor) + + with pytest.raises(RuntimeError): + pipeline.from_checkpoint() + first_processor.checkpoint_data = 1 output = pipeline.from_checkpoint() assert output == 2 + + @staticmethod + def test_pipeline_string_representation(pipeline_type: type[Pipeline]) -> None: + """Tests that the string representation of a :py:class:`~corelay.pipeline.base.Pipeline` instance is correct. + + Args: + pipeline_type (type[Pipeline]): The custom :py:class:`~corelay.pipeline.base.Pipeline` type that is to be used in the test. + """ + + pipeline = pipeline_type() + expected_string_representation = ( + "MyPipeline(\n" + " FunctionProcessor(processing_function=lambda self, x: x + 3, bind_method=True) -> numpy.ndarray\n" + " MyProcessor(is_output=True, param_1='default_value', param_2=42) -> numpy.ndarray\n" + ")" + ) + + assert repr(pipeline) == expected_string_representation diff --git a/tests/unit_tests/corelay/pipeline/test_spectral.py b/tests/unit_tests/corelay/pipeline/test_spectral.py index e1b1a06..804fb0f 100644 --- a/tests/unit_tests/corelay/pipeline/test_spectral.py +++ b/tests/unit_tests/corelay/pipeline/test_spectral.py @@ -47,6 +47,7 @@ def get_spiral_data_fixture(number_of_samples_per_class: int = 150) -> numpy.nda @pytest.fixture(name='number_of_neighbors', scope='module') def get_number_of_neighbors_fixture(spiral_data: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> int: """A fixture that choose a suitable number of neighbors for the k-nearest neighbors algorithm. + Args: spiral_data (numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]): The spiral test data. diff --git a/tests/unit_tests/corelay/processor/test_affinity.py b/tests/unit_tests/corelay/processor/test_affinity.py new file mode 100644 index 0000000..aaec28a --- /dev/null +++ b/tests/unit_tests/corelay/processor/test_affinity.py @@ -0,0 +1,62 @@ +"""A module that contains unit tests for the :py:mod:`corelay.processor.affinity` module.""" + +import typing + +import numpy +from scipy.sparse import csr_matrix +from scipy.spatial.distance import pdist, squareform + +from corelay.processor.affinity import RadialBasisFunction, SparseKNN + + +class TestSparseKNN: + """Contains unit tests for the :py:class:`corelay.processor.affinity.SparseKNN` class.""" + + @staticmethod + def test_sparse_knn_affinity() -> None: + """Tests the :py:class:`corelay.processor.affinity.SparseKNN` affinity class with symmetric k-Nearest-Neighbor (kNN).""" + + affinity = SparseKNN(n_neighbors=1) + assert affinity.n_neighbors == 1 + assert affinity.symmetric + + data_points = numpy.array([[1, 1], [1, 2], [1, 3]]) + distance_matrix: numpy.ndarray[typing.Any, typing.Any] = squareform(pdist(data_points, metric='euclidean')) + numpy.testing.assert_array_equal(distance_matrix, numpy.array([[0.0, 1.0, 2.0], [1.0, 0.0, 1.0], [2.0, 1.0, 0.0]])) + + affinity_matrix: csr_matrix = affinity(distance_matrix) + numpy.testing.assert_array_equal(affinity_matrix.toarray(), numpy.array([[0.0, 1.0, 0.0], [1.0, 0.0, 0.5], [0.0, 0.5, 0.0]])) + + @staticmethod + def test_asymmetric_sparse_knn_affinity() -> None: + """Tests the :py:class:`corelay.processor.affinity.SparseKNN` affinity class with asymmetric k-Nearest-Neighbor (kNN).""" + + affinity = SparseKNN(n_neighbors=1, symmetric=False) + assert affinity.n_neighbors == 1 + assert not affinity.symmetric + + data_points = numpy.array([[1, 1], [1, 2], [1, 3]]) + distance_matrix: numpy.ndarray[typing.Any, typing.Any] = squareform(pdist(data_points, metric='euclidean')) + numpy.testing.assert_array_equal(distance_matrix, numpy.array([[0.0, 1.0, 2.0], [1.0, 0.0, 1.0], [2.0, 1.0, 0.0]])) + + affinity_matrix: csr_matrix = affinity(distance_matrix) + numpy.testing.assert_array_equal(affinity_matrix.toarray(), numpy.array([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])) + + +class TestRadialBasisFunction: + """Contains unit tests for the :py:class:`corelay.processor.affinity.RadialBasisFunction` class.""" + + @staticmethod + def test_radial_basis_function_affinity() -> None: + """Tests the :py:class:`corelay.processor.affinity.RadialBasisFunction` affinity class.""" + + affinity = RadialBasisFunction(sigma=1.0) + assert affinity.sigma == 1.0 + + data_points = numpy.array([[1, 1], [1, 2], [1, 3]]) + distance_matrix: numpy.ndarray[typing.Any, typing.Any] = squareform(pdist(data_points, metric='euclidean')) + numpy.testing.assert_array_equal(distance_matrix, numpy.array([[0.0, 1.0, 2.0], [1.0, 0.0, 1.0], [2.0, 1.0, 0.0]])) + + affinity_matrix: numpy.ndarray[typing.Any, typing.Any] = affinity(distance_matrix) + expected_affinity = numpy.exp(-distance_matrix**2 / (2 * affinity.sigma**2)) + numpy.testing.assert_array_almost_equal(affinity_matrix, expected_affinity) diff --git a/tests/unit_tests/corelay/processor/test_base.py b/tests/unit_tests/corelay/processor/test_base.py index d340bbb..2de59d6 100644 --- a/tests/unit_tests/corelay/processor/test_base.py +++ b/tests/unit_tests/corelay/processor/test_base.py @@ -185,6 +185,28 @@ def test_unknown_param(processor_type: type[Processor]) -> None: with pytest.raises(TypeError): processor_type(param_1='bacon', parma_0='monkey') + @staticmethod + def test_too_many_positional_params(processor_type: type[Processor]) -> None: + """Tests that passing too many positional arguments raises an exception. + + Args: + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. + """ + + with pytest.raises(TypeError): + processor_type(44, 21, param1='test', param3=1) + + @staticmethod + def test_positional_param_also_supplied_as_keyword_argument(processor_type: type[Processor]) -> None: + """Tests that passing a positional parameter as a keyword argument raises an exception. + + Args: + processor_type (type[Processor]): The custom :py:class:`~corelay.processor.base.Processor` type that is to be used in the test. + """ + + with pytest.raises(TypeError): + processor_type(44, param1='test', param_2=22, param3='bacon') + @staticmethod def test_abstract_func() -> None: """Tests that the :py:class:`~corelay.processor.base.Processor` class is abstract and thus fails to instantiate.""" diff --git a/tests/unit_tests/corelay/processor/test_clustering.py b/tests/unit_tests/corelay/processor/test_clustering.py index ac651f7..0cc0308 100644 --- a/tests/unit_tests/corelay/processor/test_clustering.py +++ b/tests/unit_tests/corelay/processor/test_clustering.py @@ -1,5 +1,6 @@ """A module that contains unit tests for the :py:mod:`corelay.processor.clustering` module.""" +from io import BytesIO import os import typing from importlib import import_module @@ -90,6 +91,8 @@ def test_clustering(processor_type: type[clustering.Clustering], data: numpy.nda @pytest.mark.parametrize('processor_type', EXTRA_PROCESSORS + [clustering.DBSCAN]) +@pytest.mark.filterwarnings('ignore:using precomputed metric; inverse_transform will be unavailable') +@pytest.mark.filterwarnings('ignore:invalid escape sequence') def test_embedding_on_distances( processor_type: type[clustering.Clustering], distances: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] @@ -157,3 +160,11 @@ def test_dendrogram_creation_with_file_object(tiny_data: numpy.ndarray[typing.An assert os.path.exists(output_path) os.remove(output_path) + + +def test_dendrogram_invalid_linkage_method_fails() -> None: + """Tests the creation of a dendrogram for the specified data fails if the specified linkage method is not supported.""" + + dendrogram_processor = clustering.Dendrogram(output_file=BytesIO(), linkage='invalid_linkage_method') + with pytest.raises(ValueError): + dendrogram_processor(numpy.array([])) diff --git a/tests/unit_tests/corelay/processor/test_distance.py b/tests/unit_tests/corelay/processor/test_distance.py new file mode 100644 index 0000000..1b84a75 --- /dev/null +++ b/tests/unit_tests/corelay/processor/test_distance.py @@ -0,0 +1,27 @@ +"""A module that contains unit tests for the :py:mod:`corelay.processor.distance` module.""" + +import typing + +import numpy +import pytest + +from corelay.processor.distance import SciPyPDist + + +def test_scipy_distance_processor() -> None: + """Tests the :py:class:`corelay.processor.distance.ScipyDistanceProcessor` processor.""" + + processor = SciPyPDist(metric='euclidean') + data_points = numpy.array([[1, 1], [1, 2], [1, 3]]) + distance_matrix: numpy.ndarray[typing.Any, typing.Any] = processor(data_points) + numpy.testing.assert_array_equal(distance_matrix, numpy.array([[0.0, 1.0, 2.0], [1.0, 0.0, 1.0], [2.0, 1.0, 0.0]])) + + +def test_scipy_distance_processor_invalid_metric() -> None: + """Tests the :py:class:`corelay.processor.distance.ScipyDistanceProcessor` processor with an invalid metric.""" + + processor = SciPyPDist(metric='invalid_metric') + + data_points = numpy.array([[1, 1], [1, 2], [1, 3]]) + with pytest.raises(ValueError): + processor(data_points) diff --git a/tests/unit_tests/corelay/processor/test_embedding.py b/tests/unit_tests/corelay/processor/test_embedding.py index 8826f13..a843e73 100644 --- a/tests/unit_tests/corelay/processor/test_embedding.py +++ b/tests/unit_tests/corelay/processor/test_embedding.py @@ -84,13 +84,31 @@ def test_eigen_decomposition(distances: numpy.ndarray[typing.Any, numpy.dtype[nu """ eigen_decomposition = embedding.EigenDecomposition(n_eigval=32) - eigenvalues, eigenvector = eigen_decomposition(distances) + eigenvalues, eigenvectors = eigen_decomposition(distances) assert eigenvalues.shape == (32, ) - assert eigenvector.shape == (360, 32) + assert eigenvectors.shape == (360, 32) + numpy.testing.assert_array_almost_equal(numpy.linalg.norm(eigenvectors, axis=1, keepdims=True), numpy.ones((360, 1))) + + eigen_decomposition = embedding.EigenDecomposition(n_eigval=32, normalize=False) + eigenvalues, eigenvectors = eigen_decomposition(distances) + + assert eigenvalues.shape == (32, ) + assert eigenvectors.shape == (360, 32) + assert numpy.sum(numpy.abs(numpy.linalg.norm(eigenvectors, axis=1, keepdims=True) - numpy.ones((360, 1)))).item() != 0.0 + + +def test_eigen_decomposition_invalid_metric() -> None: + """Tests the eigen-decomposition processor with an invalid metric.""" + + eigen_decomposition = embedding.EigenDecomposition(which='invalid_metric') + + with pytest.raises(ValueError): + eigen_decomposition(numpy.random.rand(360, 360)) @pytest.mark.parametrize('processor_type', EXTRA_PROCESSORS + [embedding.TSNEEmbedding]) +@pytest.mark.filterwarnings('ignore:using precomputed metric; inverse_transform will be unavailable') def test_embedding_on_distances(processor_type: type[Processor], distances: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> None: """Tests the embedding processors on pre-computed distances and checks the dimensions. @@ -104,3 +122,9 @@ def test_embedding_on_distances(processor_type: type[Processor], distances: nump output_embedding = processor(distances) assert output_embedding.shape == (360, 2) + +def test_eigen_decomposition_output_representation() -> None: + """Tests the output representation of the eigen-decomposition processor.""" + + eigen_decomposition = embedding.EigenDecomposition() + assert repr(eigen_decomposition).endswith('(eigval: numpy.ndarray, eigvec: numpy.ndarray)') diff --git a/tests/unit_tests/corelay/processor/test_flow.py b/tests/unit_tests/corelay/processor/test_flow.py index d25bb47..5387b78 100644 --- a/tests/unit_tests/corelay/processor/test_flow.py +++ b/tests/unit_tests/corelay/processor/test_flow.py @@ -44,6 +44,40 @@ def test_stacked_multiple_levels() -> None: shaper = Shaper(indices=(0, (1, (2,)))) assert shaper([1, 2, 3]) == (1, (2, (3,))) + @staticmethod + def test_non_sequence_data() -> None: + """Tests that non-sequence data is handled as if it were a sequence of length 1.""" + + shaper = Shaper(indices=(0,)) + assert shaper(1) == (1,) + + @staticmethod + def test_out_of_bounds_indices() -> None: + """Tests that out-of-bounds indices raise a :py:class:`~corelay.processor.base.ProcessorError`.""" + + shaper = Shaper(indices=(1, 2, 3)) + with pytest.raises(TypeError): + shaper([1, 2]) + + @staticmethod + def test_invalid_indices() -> None: + """Tests that invalid indices raise a :py:class:`~corelay.processor.base.ProcessorError`.""" + + shaper = Shaper(indices=('one', 'two', 'three')) + with pytest.raises(TypeError): + shaper([1, 2, 3, 4]) + + @staticmethod + def test_invalid_indices_dictionary() -> None: + """Tests that invalid indices for a dictionary raise a :py:class:`~corelay.processor.base.ProcessorError`.""" + + shaper = Shaper(indices=('one', 'two', 'three')) + with pytest.raises(TypeError): + shaper({ + 'one': 1, + 'two': 2 + }) + class TestParallel: """Contains unit tests for the :py:class:`~corelay.processor.flow.Parallel` class.""" diff --git a/tests/unit_tests/corelay/processor/test_laplacian.py b/tests/unit_tests/corelay/processor/test_laplacian.py new file mode 100644 index 0000000..15b9e34 --- /dev/null +++ b/tests/unit_tests/corelay/processor/test_laplacian.py @@ -0,0 +1,67 @@ +"""A module that contains unit tests for the :py:mod:`corelay.processor.laplacian` module.""" + +import numpy +import pytest +import scipy +from sklearn.datasets import make_blobs + +from corelay.processor.affinity import SparseKNN +from corelay.processor.distance import SciPyPDist +from corelay.processor.laplacian import RandomWalkNormalLaplacian, SymmetricNormalLaplacian, a1ifmat + + +@pytest.mark.filterwarnings('ignore:the matrix subclass is not the recommended way to represent matrices') +def test_a1ifmat() -> None: + """Tests the :py:func:`corelay.processor.laplacian.a1ifmat` function.""" + + array = numpy.array([[1, 2], [3, 4]]) + matrix = numpy.matrix(array) + + assert numpy.array_equal(array, a1ifmat(array)) + assert numpy.array_equal(array.ravel(), a1ifmat(matrix)) + + +def test_symmetric_normal_laplacian() -> None: + """Tests the symmetric normal laplacian processor.""" + + samples, _ = make_blobs(n_samples=100, n_features=2, centers=5) # pylint: disable=unbalanced-tuple-unpacking + + euclidean_distance = SciPyPDist(metric='euclidean') + distance_matrix = euclidean_distance(samples) + + sparse_knn = SparseKNN(n_neighbors=5, symmetric=True) + affinity_matrix = sparse_knn(distance_matrix) + + manual_degree = scipy.sparse.diags(affinity_matrix.sum(axis=1).A1**-0.5, 0) + manual_laplacian = manual_degree @ affinity_matrix @ manual_degree + + symmetric_normal_laplacian = SymmetricNormalLaplacian() + laplacian = symmetric_normal_laplacian(affinity_matrix) + + numpy.testing.assert_array_equal( + numpy.array(manual_laplacian.todense()), + numpy.array(laplacian.todense()) + ) + + +def test_symmetric_random_walk_laplacian() -> None: + """Tests the symmetric random walk laplacian processor.""" + + samples, _ = make_blobs(n_samples=100, n_features=2, centers=5) # pylint: disable=unbalanced-tuple-unpacking + + euclidean_distance = SciPyPDist(metric='euclidean') + distance_matrix = euclidean_distance(samples) + + sparse_knn = SparseKNN(n_neighbors=5, symmetric=True) + affinity_matrix = sparse_knn(distance_matrix) + + manual_degree = scipy.sparse.diags(affinity_matrix.sum(axis=1).A1**-1.0, 0) + manual_laplacian = manual_degree @ affinity_matrix + + symmetric_random_walk_laplacian = RandomWalkNormalLaplacian() + laplacian = symmetric_random_walk_laplacian(affinity_matrix) + + numpy.testing.assert_array_equal( + numpy.array(manual_laplacian.todense()), + numpy.array(laplacian.todense()) + ) diff --git a/tests/unit_tests/corelay/processor/test_preprocessing.py b/tests/unit_tests/corelay/processor/test_preprocessing.py index 6078a59..7ec7c18 100644 --- a/tests/unit_tests/corelay/processor/test_preprocessing.py +++ b/tests/unit_tests/corelay/processor/test_preprocessing.py @@ -5,7 +5,7 @@ import numpy import pytest -from corelay.processor.preprocessing import Rescale, Resize, Pooling +from corelay.processor.preprocessing import Histogram, Rescale, Resize, Pooling @pytest.fixture(name='no_channels', scope='module') @@ -135,3 +135,68 @@ def test_pooling(fixture_name: str, shape: tuple[int, ...], stride: tuple[int, . assert output_data.shape == shape numpy.testing.assert_equal(output_data, 4 * numpy.ones(output_data.shape)) + + +def test_histogram() -> None: + """Tests the histogram pre-processing processor.""" + + processor = Histogram(bins=2) + input_data = numpy.array([[ + [[1, 2], + [3, 4]], + [[1, 2], + [3, 4]], + [[1, 2], + [3, 4]] + ]]) + + output_data = processor(input_data) + assert output_data.shape == (1, 3, 2) + + expected_histogram = numpy.array([[ + [1.0 / 3.0, 1.0 / 3.0], + [1.0 / 3.0, 1.0 / 3.0], + [1.0 / 3.0, 1.0 / 3.0] + ]]) + numpy.testing.assert_equal(output_data, expected_histogram) + + +def test_histogram_with_channel_last_input() -> None: + """Tests the histogram pre-processing processor with images were the channel dimension comes last.""" + + processor = Histogram(bins=2, channels_first=False) + input_data = numpy.array([[[ + [1, 1, 1], + [2, 2, 2] + ], [ + [3, 3, 3], + [4, 4, 4] + ]]]) + + output_data = processor(input_data) + assert output_data.shape == (1, 3, 2) + + expected_histogram = numpy.array([[ + [1.0 / 3.0, 1.0 / 3.0], + [1.0 / 3.0, 1.0 / 3.0], + [1.0 / 3.0, 1.0 / 3.0] + ]]) + numpy.testing.assert_equal(output_data, expected_histogram) + + +def test_histogram_with_grayscale_input() -> None: + """Tests the histogram pre-processing processor with grayscale images.""" + + processor = Histogram(bins=2) + input_data = numpy.array([[ + [1, 2], + [3, 4] + ]]) + + output_data = processor(input_data) + assert output_data.shape == (1, 1, 2) + + expected_histogram = numpy.array([[ + [1.0 / 3.0, 1.0 / 3.0] + ]]) + numpy.testing.assert_equal(output_data, expected_histogram) diff --git a/tests/unit_tests/corelay/test_base.py b/tests/unit_tests/corelay/test_base.py index 0ea6d6e..a0d5ee2 100644 --- a/tests/unit_tests/corelay/test_base.py +++ b/tests/unit_tests/corelay/test_base.py @@ -46,3 +46,16 @@ def test_dtype_single_to_tuple() -> None: param = Param(object) assert param.dtype == (object,) + + @staticmethod + def test_string_representation_of_param() -> None: + """Tests that the string representation of a :py:class:`~corelay.base.Param` instance is correct. Sphinx AutoDoc uses :py:func:`repr` when it + encounters :py:class:`typing.Annotated`, which in turn uses :py:func:`repr` to get a string representation of its metadata. This is a + reasonable thing to do, but then Intersphinx tries to resolve the resulting string as types for cross-referencing, which is not possible with + the default implementation of :py:meth:`object.__repr__`. To be able to get proper documentation, the fully-qualified name of the class needs + to be returned, because this enable Sphinx AutoDoc to reference the class in the documentation. The tilde in front is interpreted by AutoDoc + to mean that only the last part of the fully-qualified name should be displayed in the documentation. + """ + + param = Param(object) + assert repr(param) == '~corelay.base.Param' diff --git a/tests/unit_tests/corelay/test_plugboard.py b/tests/unit_tests/corelay/test_plugboard.py index 1e548a2..1766300 100644 --- a/tests/unit_tests/corelay/test_plugboard.py +++ b/tests/unit_tests/corelay/test_plugboard.py @@ -1,5 +1,7 @@ """A module that contains unit tests for the :py:mod:`corelay.plugboard` module.""" +from typing import Annotated + import pytest from corelay.plugboard import Slot, Plug, Plugboard @@ -38,12 +40,13 @@ def test_init_unknown_args() -> None: def test_init_class_name() -> None: """Tests that when instantiating a class, the __name__ parameter of a contained :py:class:`~corelay.plugboard.Slot` is set accordingly.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot() + my_slot: Annotated[object, Slot()] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) assert SlotHolder.my_slot.__name__ == 'my_slot' @staticmethod @@ -52,10 +55,10 @@ def test_init_instance_default() -> None: returned. """ - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(default=42) + my_slot: Annotated[int, Slot(int, default=42)] """A test slot.""" slot_holder = SlotHolder() @@ -65,10 +68,10 @@ class SlotHolder: def test_instance_get() -> None: """Tests that getting a value after setting it succeeds.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int) + my_slot: Annotated[int, Slot(dtype=int)] """A test slot.""" slot_holder = SlotHolder() @@ -79,10 +82,10 @@ class SlotHolder: def test_instance_get_no_default() -> None: """Tests that when accessing a :py:class:`~corelay.plugboard.Slot` in an instance, where nothing is set, an exception is raised.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot() + my_slot: Annotated[object, Slot()] """A test slot.""" slot_holder = SlotHolder() @@ -94,10 +97,10 @@ class SlotHolder: def test_instance_set() -> None: """Tests that everything works alright when not setting a default value, but then setting the value of the object before accessing it.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int) + my_slot: Annotated[int, Slot(dtype=int)] """A test slot.""" slot_holder = SlotHolder() @@ -109,25 +112,25 @@ class SlotHolder: def test_instance_set_wrong_dtype() -> None: """Tests that setting a value with the wrong data type raises an exception.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(str) + my_slot: Annotated[str, Slot(str)] """A test slot.""" slot_holder = SlotHolder() with pytest.raises(TypeError): - slot_holder.my_slot = 15 + slot_holder.my_slot = 15 # type: ignore[assignment] @staticmethod def test_instance_delete_unchanged() -> None: """Tests that setting a value and deleting it afterwards with a set default value returns the default value""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(int, 42) + my_slot: Annotated[int, Slot(int, 42)] """A test slot.""" slot_holder = SlotHolder() @@ -140,10 +143,10 @@ class SlotHolder: def test_instance_delete_without_default() -> None: """Tests that setting a value and deleting it afterwards without a default value raises an exception.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(int) + my_slot: Annotated[int, Slot(int)] """A test slot.""" slot_holder = SlotHolder() @@ -156,24 +159,26 @@ class SlotHolder: def test_class_set_dtype() -> None: """Tests that setting a new data type that is consistent with the data type of the already existing default value succeeds.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=object, default=15) + my_slot: Annotated[object, Slot(dtype=object, default=15)] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) SlotHolder.my_slot.dtype = int @staticmethod def test_class_set_dtype_inconsistent() -> None: """Tests that setting an new data type that is not consistent with the already existing default value fails.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=object, default=15) + my_slot: Annotated[object, Slot(dtype=object, default=15)] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) with pytest.raises(TypeError): SlotHolder.my_slot.dtype = str @@ -181,24 +186,26 @@ class SlotHolder: def test_class_optional() -> None: """Tests that slots with default values are optional.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) assert SlotHolder.my_slot.optional @staticmethod def test_class_not_optional() -> None: """Tests that slots without default values are not optional.""" - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int) + my_slot: Annotated[int, Slot(dtype=int)] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) assert not SlotHolder.my_slot.optional @staticmethod @@ -207,12 +214,13 @@ def test_class_call() -> None: :py:class:`~corelay.plugboard.Slot`. """ - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) assert SlotHolder.my_slot is SlotHolder.my_slot().slot @staticmethod @@ -221,12 +229,13 @@ def test_class_call_obj() -> None: :py:attr:`~corelay.plugboard.Plug.obj` property set to that value. """ - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int) + my_slot: Annotated[int, Slot(dtype=int)] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) assert SlotHolder.my_slot(obj=15).obj == 15 @staticmethod @@ -235,14 +244,57 @@ def test_class_call_default() -> None: :py:attr:`~corelay.plugboard.Plug.default` property set. """ - class SlotHolder: + class SlotHolder(Plugboard): """A test class that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int) + my_slot: Annotated[int, Slot(dtype=int)] """A test slot.""" + assert isinstance(SlotHolder.my_slot, Slot) assert SlotHolder.my_slot(default=15).default == 15 + @staticmethod + def test_default_set() -> None: + """Tests that setting the :py:attr:`~corelay.plugboard.Slot.default` property of a :py:class:`~corelay.plugboard.Slot` to a value that is + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` succeeds. + """ + + slot = Slot(dtype=str, default='fallback') + slot.default = 'default' + + @staticmethod + def test_default_set_inconsistent() -> None: + """Tests that setting the :py:attr:`~corelay.plugboard.Slot.default` property of a :py:class:`~corelay.plugboard.Slot` to a value that is not + consistent with the data type of the :py:class:`~corelay.plugboard.Slot` fails. + """ + + slot = Slot(dtype=str, default='fallback') + + with pytest.raises(TypeError): + slot.default = 15 + + @staticmethod + def test_default_del() -> None: + """Tests that deleting the value of the :py:attr:`~corelay.plugboard.Plug.default` property of a :py:class:`~corelay.plugboard.Plug` that is + associated with a :py:class:`~corelay.plugboard.Slot` that has a :py:attr:`~corelay.plugboard.Plug.default` value succeeds. + """ + + slot = Slot(dtype=str, default='fallback') + del slot.default + + @staticmethod + def test_string_representation_of_slot() -> None: + """Tests that the string representation of a :py:class:`~corelay.base.Param` instance is correct. Sphinx AutoDoc uses :py:func:`repr` when it + encounters :py:class:`typing.Annotated`, which in turn uses :py:func:`repr` to get a string representation of its metadata. This is a + reasonable thing to do, but then Intersphinx tries to resolve the resulting string as types for cross-referencing, which is not possible with + the default implementation of :py:meth:`object.__repr__`. To be able to get proper documentation, the fully-qualified name of the class needs + to be returned, because this enable Sphinx AutoDoc to reference the class in the documentation. The tilde in front is interpreted by AutoDoc + to mean that only the last part of the fully-qualified name should be displayed in the documentation. + """ + + slot = Slot() + assert repr(slot) == '~corelay.plugboard.Slot' + class TestPlug: """Contains unit tests for the :py:class:`~corelay.plugboard.Plug` class.""" @@ -618,7 +670,7 @@ def test_init_assign() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard(my_slot=19) @@ -634,7 +686,7 @@ def test_default_get() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard(my_slot=19) @@ -650,7 +702,7 @@ def test_default_set() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard() @@ -667,7 +719,7 @@ def test_default_set_dict() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard() @@ -684,7 +736,7 @@ def test_default_set_dict_wrong() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard() @@ -701,7 +753,7 @@ def test_default_del() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard() @@ -710,6 +762,20 @@ class MyPlugboard(Plugboard): assert plugboard.my_slot == 15 + @staticmethod + def test_default_del_non_existing_attribute() -> None: + """Tests that deleting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` fails, if the + attribute does not exist in the :py:class:`~corelay.plugboard.Plugboard`. + """ + + class MyPlugboard(Plugboard): + """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" + + plugboard = MyPlugboard() + + with pytest.raises(AttributeError): + del plugboard.default.non_existing_slot + @staticmethod def test_default_set_influence_obj() -> None: """Tests that setting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` does not @@ -719,7 +785,7 @@ def test_default_set_influence_obj() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard(my_slot=19) @@ -735,7 +801,7 @@ def test_default_dir() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard() @@ -752,14 +818,19 @@ def test_update_defaults() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" + non_slot: str = 'This is not a slot.' + """A non-slot attribute for testing what happens when a non-slot's default is updated.""" + plugboard = MyPlugboard() plugboard.update_defaults(my_slot=17) - assert plugboard.my_slot == 17 + with pytest.raises(AttributeError): + plugboard.update_defaults(non_slot='This will fail.') + @staticmethod def test_reset_defaults() -> None: """Tests that resetting the default value of a :py:class:`~corelay.plugboard.Slot` in a :py:class:`~corelay.plugboard.Plugboard` succeeds.""" @@ -767,7 +838,7 @@ def test_reset_defaults() -> None: class MyPlugboard(Plugboard): """A test plugboard that holds a single :py:class:`~corelay.plugboard.Slot`.""" - my_slot = Slot(dtype=int, default=15) + my_slot: Annotated[int, Slot(dtype=int, default=15)] """A test slot.""" plugboard = MyPlugboard() diff --git a/tests/unit_tests/corelay/test_tracker.py b/tests/unit_tests/corelay/test_tracker.py index b317927..30f99d0 100644 --- a/tests/unit_tests/corelay/test_tracker.py +++ b/tests/unit_tests/corelay/test_tracker.py @@ -2,11 +2,13 @@ import pytest +from corelay.base import Param +from corelay.plugboard import Slot from corelay.tracker import Tracker @pytest.fixture(name='tracker_type', scope='module') -def get_tracked_fixture() -> type[Tracker]: +def get_tracker_type_fixture() -> type[Tracker]: """Creates a sub-class of :py:class:`~corelay.tracker.Tracker` with some attributes. Returns: @@ -20,26 +22,26 @@ class SubTracked(Tracker): attr_2 = 'apple' attr_3 = object attr_4 = 15 - attr_5 = 'pear' + attr_5 = None attr_6 = str return SubTracked @pytest.fixture(name='values', scope='module') -def get_values_fixture() -> dict[str, int | str | type]: +def get_values_fixture() -> dict[str, int | str | type | None]: """Generates a list of values, that can be used to test the collection of values. Returns: - dict[str, int | str | type]: Returns a :py:class:`dict` with values that can be used to test the collection of values. + dict[str, int | str | type | None]: Returns a :py:class:`dict` with values that can be used to test the collection of values. """ - result: dict[str, int | str | type] = { + result: dict[str, int | str | type | None] = { 'attr_1': 42, 'attr_2': 'apple', 'attr_3': object, 'attr_4': 15, - 'attr_5': 'pear', + 'attr_5': None, 'attr_6': str } return result @@ -82,6 +84,55 @@ def get_instance_fixture(tracker_type: type[Tracker], attributes: dict[str, str] return result +class TestMetaTracker: + """Contains unit tests for the :py:class:`~corelay.tracker.MetaTracker` meta class.""" + + @staticmethod + def test_warning_is_raised_when_old_slot_syntax_is_used() -> None: + """Tests that a warning is raised when the old slot syntax is used.""" + + with pytest.warns(DeprecationWarning, match='The Param "param" was declared using the old syntax of declaring slots, which is deprecated.'): + + class OldSlotSyntaxTracker(Tracker): + """A test tracker that uses the old slot syntax.""" + + param = Param(int, default=42) + """A slot that is declared using the old syntax.""" + + _ = OldSlotSyntaxTracker() + + @staticmethod + def test_is_slot_check_with_complex_inheritance_hierarchy() -> None: + """Tests that the :py:meth:`~corelay.tracker.MetaTracker.__new__.is_slot` method works correctly with a complex inheritance hierarchy.""" + + class Test: + """A base class that is not a :py:class:`~corelay.plugboard.Slot`.""" + + class Baz(Slot): + """A base class that is a :py:class:`~corelay.plugboard.Slot`.""" + + class Bar(Baz, Test): + """A class that inherits from :py:class:`Baz` and therefore is a :py:class:`~corelay.plugboard.Slot` indirectly.""" + + class Foo(Test): + """A class that only inherits from :py:class:`Test` and therefore is not a :py:class:`~corelay.plugboard.Slot`.""" + + class FooBar(Foo, Bar): + """A class that inherits from :py:class:`Foo` and :py:class:`Bar`, and therefore is a :py:class:`~corelay.plugboard.Slot` indirectly.""" + + with pytest.warns( + DeprecationWarning, + match='The FooBar "foo_bar" was declared using the old syntax of declaring slots, which is deprecated.' + ): + + class OldSlotSyntaxTracker(Tracker): + """A test tracker that uses the old slot syntax.""" + + foo_bar = FooBar(int, default=42) + """A slot that is declared using the old syntax.""" + + _ = OldSlotSyntaxTracker() + class TestTracker: """Contains unit tests for the :py:class:`~corelay.tracker.Tracker` class.""" @@ -94,19 +145,38 @@ def test_collect(tracker_type: type[Tracker]) -> None: """ assert tracker_type.collect(int) == {'attr_1': 42, 'attr_4': 15} - assert tracker_type.collect(str) == {'attr_2': 'apple', 'attr_5': 'pear'} + assert tracker_type.collect(str) == {'attr_2': 'apple'} assert tracker_type.collect(type) == {'attr_3': object, 'attr_6': str} + assert tracker_type.collect(type(None)) == {'attr_5': None} @staticmethod - def test_collect_multiple(tracker_type: type[Tracker], values: dict[str, int | str | type]) -> None: + def test_get(tracker_type: type[Tracker]) -> None: + """Tests that parameters of different types are collected correctly. + + Args: + tracker_type (type[Tracker]): The type of the tracker to be used for collecting the parameter values. + """ + + assert tracker_type.get('attr_1') == 42 + assert tracker_type.get('attr_2') == 'apple' + assert tracker_type.get('attr_3') == object + assert tracker_type.get('attr_4') == 15 + assert tracker_type.get('attr_5') is None + assert tracker_type.get('attr_6') == str + + with pytest.raises(AttributeError): + tracker_type.get('non_existent_attribute') + + @staticmethod + def test_collect_multiple(tracker_type: type[Tracker], values: dict[str, int | str | type | None]) -> None: """Tests that collecting parameters of multiple data types succeeds. Args: tracker_type (type[Tracker]): The type of the tracker to be used for collecting the parameter values. - values (dict[str, int | str | type]): The expected values to be collected. + values (dict[str, int | str | type | None]): The expected values to be collected. """ - assert tracker_type.collect((int, str, type)) == values + assert tracker_type.collect((int, str, type, type(None))) == values @staticmethod def test_collect_attr(instance: Tracker, attributes: dict[str, str]) -> None: @@ -117,4 +187,19 @@ def test_collect_attr(instance: Tracker, attributes: dict[str, str]) -> None: attributes (dict[str, str]): The expected values to be collected. """ - assert instance.collect_attr((int, str, type)) == attributes + assert instance.collect_attr((int, str, type, type(None))) == attributes + + @staticmethod + def test_get_attr(instance: Tracker, attributes: dict[str, str]) -> None: + """Tests that collecting instance attribute values of owner attributes succeeds. + + Args: + instance (Tracker): The instance of the tracker to be used for collecting the parameter values. + attributes (dict[str, str]): The expected values to be collected. + """ + + for attribute_name, attribute_value in attributes.items(): + assert instance.get_attr(attribute_name) == attribute_value + + with pytest.raises(AttributeError): + instance.get_attr('non_existent_attribute') diff --git a/tests/unit_tests/corelay/test_utils.py b/tests/unit_tests/corelay/test_utils.py index 6143230..302b283 100644 --- a/tests/unit_tests/corelay/test_utils.py +++ b/tests/unit_tests/corelay/test_utils.py @@ -1,8 +1,195 @@ """A module that contains unit tests for the :py:mod:`corelay.utils` module.""" +import re +import sys +from types import LambdaType +from typing import Any, Callable + +import numpy +import numpy.testing import pytest -from corelay.utils import import_or_stub, zip_equal +from corelay.utils import get_fully_qualified_name, get_lambda_expression_source_code, get_object_representation, import_or_stub, zip_equal + + +def test_get_lambda_expression_source_code() -> None: + """Tests that the :py:func:`~corelay.utils.get_lambda_expression_source_code` function returns the correct source code of a lambda expression.""" + + # Test that the function returns the correct source code of a lambda expression that is passed as a parameter + assert get_lambda_expression_source_code(lambda a, b: a + b) == 'lambda a, b: a + b' + + # Test that the function returns the correct source code of a lambda expression that is defined as a variable + lambda_expression = lambda x, y: x * y + assert get_lambda_expression_source_code(lambda_expression) == 'lambda x, y: x * y' + + # Test that the function returns the correct source code of a lambda expression that is defined as a default parameter or passed as a parameter to + # a function + def test_function_with_lambda_expression_as_parameter(lambda_expression_parameter: Callable[..., Any] = lambda x, y: x + y) -> str: + """A test function that takes a lambda expression as a parameter and returns its string representation. + + Args: + lambda_expression_parameter (Callable[..., Any]): A lambda expression to be represented as a string. Defaults to a simple addition lambda. + + Returns: + str: Returns the string representation of the lambda expression.""" + + assert isinstance(lambda_expression_parameter, LambdaType) + return get_object_representation(lambda_expression_parameter) + + assert test_function_with_lambda_expression_as_parameter() == 'lambda x, y: x + y' + assert test_function_with_lambda_expression_as_parameter(lambda a, b: a * b) == 'lambda a, b: a * b' + + # Tests that the function returns the object representation for a non-lambda object + assert get_lambda_expression_source_code(123) == '123' # type: ignore[arg-type] + + # Tests that the function returns the object representation if a function is passed instead of a lambda expression + def test_function_used_instead_of_lambda(a: int, b: int) -> int: + """A test function that adds two integers. + + Args: + a (int): The first integer. + b (int): The second integer. + + Returns: + int: Returns the sum of the two integers. + """ + + return a + b + + assert re.fullmatch( + '.test_function_used_instead_of_lambda at 0x[0-9a-f]+>', + get_lambda_expression_source_code(test_function_used_instead_of_lambda) + ) is not None + + # Tests that the function returns the correct source code for a lambda expression if there are two lambda expressions in a line, but they differ + # in their parameters + lambda_expressions = (lambda x, y: x + y, lambda a, b: a * b) + assert get_lambda_expression_source_code(lambda_expressions[0]) == 'lambda x, y: x + y' + assert get_lambda_expression_source_code(lambda_expressions[1]) == 'lambda a, b: a * b' + + # Tests that the function returns the correct source code for a lambda expression if there are two lambda expressions in a line that do not differ + # in their parameters + lambda_expressions = (lambda x, y: x + y, lambda x, y: x * y) + assert get_lambda_expression_source_code(lambda_expressions[0]) == 'lambda x, y: x + y' + assert get_lambda_expression_source_code(lambda_expressions[1]) == 'lambda x, y: x * y' + + # Tests that the function returns the correct source code for a lambda expression if there are two lambda expressions in a line that do not differ + # in their parameters, that use a global variable, and that have a different complexity (since the closure environment will be different when the + # get_lambda_expression_source_code function compiles the lambda expressions, the byte code will be different, so the byte code cannot be used to + # differentiate the two; since they have different complexity, the complexity of the byte code can be used to differentiate between them) + global_var = 42 + lambda_expressions = (lambda x, y: x + y + global_var, lambda x, y: sum([x, y, global_var])) + assert get_lambda_expression_source_code(lambda_expressions[0]) == 'lambda x, y: x + y + global_var' + assert get_lambda_expression_source_code(lambda_expressions[1]) == 'lambda x, y: sum([x, y, global_var])' + + # Tests that the function returns the object representation for two lambda expressions that do not differ in their parameters, use a global + # variable, differ in their implementation, but have the same complexity + lambda_expressions = (lambda x, y: x + y + global_var, lambda x, y: x * y * global_var) + assert re.fullmatch( + '. at 0x[0-9a-f]+>', + get_lambda_expression_source_code(lambda_expressions[0]) + ) is not None + assert re.fullmatch( + '. at 0x[0-9a-f]+>', + get_lambda_expression_source_code(lambda_expressions[1]) + ) is not None + + +def test_get_object_representation() -> None: + """Tests that the :py:func:`~corelay.utils.get_object_representation` function returns the correct string representation of various objects.""" + + # Test lambda expressions + lambda_expression = lambda a, b: a + b + assert get_object_representation(lambda_expression) == 'lambda a, b: a + b' + assert get_object_representation(lambda x: x) == 'lambda x: x' + + # Test functions, methods, classes, and modules + assert get_object_representation(test_get_object_representation) == 'unit_tests.corelay.test_utils.test_get_object_representation' + assert get_object_representation(object().__str__) == 'object.__str__' + assert get_object_representation(sys) == 'sys' + assert get_object_representation(sys.exit) == 'sys.exit' + assert get_object_representation(len) == 'len' + assert get_object_representation(None) == 'None' + assert get_object_representation(int) == 'int' + assert get_object_representation(str) == 'str' + assert get_object_representation(list) == 'list' + + # Test values + assert get_object_representation(123) == '123' + assert get_object_representation('test') == "'test'" + assert get_object_representation([]) == '[]' + assert get_object_representation({}) == '{}' + assert get_object_representation({'key': 'value'}) == "{'key': 'value'}" + assert get_object_representation([1, 2, 3]) == '[1, 2, 3]' + assert get_object_representation((1, 2, 3)) == '(1, 2, 3)' + assert get_object_representation({1, 2, 3}) == '{1, 2, 3}' + + +def test_get_fully_qualified_name() -> None: + """Tests that the :py:func:`~corelay.utils.get_fully_qualified_name` function correctly returns the fully-qualified name of values, types, and + built-ins. + """ + + # Functions + assert get_fully_qualified_name(test_get_fully_qualified_name) == 'unit_tests.corelay.test_utils.test_get_fully_qualified_name' + + # Types and Methods + class TestClass: + """A test class for testing the retrieval of fully qualified names.""" + + def test_method(self) -> None: + """A test method for testing the retrieval of fully qualified names of methods.""" + + assert get_fully_qualified_name(TestClass) == 'unit_tests.corelay.test_utils.test_get_fully_qualified_name..TestClass' + assert (get_fully_qualified_name(TestClass().test_method) == + 'unit_tests.corelay.test_utils.test_get_fully_qualified_name..TestClass.test_method') + TestClass.__module__ = '__main__' # Simulate a class defined in the main module + assert get_fully_qualified_name(TestClass().test_method) == 'test_get_fully_qualified_name..TestClass.test_method' + + # Modules + assert get_fully_qualified_name(sys) == 'sys' + assert get_fully_qualified_name(numpy.testing) == 'numpy.testing' + + # Built-in functions + assert get_fully_qualified_name(sys.exit) == 'sys.exit' + assert get_fully_qualified_name(len) == 'len' + + # Lambda functions + lambda_expression = lambda x: x + assert get_fully_qualified_name(lambda_expression) == 'unit_tests.corelay.test_utils.test_get_fully_qualified_name..' + assert get_fully_qualified_name(lambda x: x) == 'unit_tests.corelay.test_utils.test_get_fully_qualified_name..' + + # None is of type 'NoneType', but we want to return 'None' for it + assert get_fully_qualified_name(None) == 'None' + + # Built-in method wrappers + assert get_fully_qualified_name(object().__str__) == 'object.__str__' + + # Built-in method descriptors + assert get_fully_qualified_name(str.join) == 'str.join' + + # Built-in class method descriptors + assert get_fully_qualified_name(dict.__dict__['fromkeys']) == 'dict.fromkeys' + + # Built-in method wrapper type + assert get_fully_qualified_name(object().__str__) == 'object.__str__' + + # Built-in wrapper descriptors + assert get_fully_qualified_name(object.__init__) == 'object.__init__' + + # Built-in types + assert get_fully_qualified_name(int) == 'int' + assert get_fully_qualified_name(str) == 'str' + assert get_fully_qualified_name(123) == 'int' + assert get_fully_qualified_name('test') == 'str' + assert get_fully_qualified_name([]) == 'list' + + # Built-in methods + assert get_fully_qualified_name('test'.lower) == 'str.lower' + + # NumPy array functions and NunPy universal functions + assert get_fully_qualified_name(numpy.min) == 'numpy.min' + assert get_fully_qualified_name(numpy.sin) == 'numpy.sin' def test_conditional_import() -> None: @@ -10,15 +197,15 @@ def test_conditional_import() -> None: non_existing_module = import_or_stub('non_existing_module') non_existing_function = import_or_stub('non_existing_module', 'non_existing_function') - re = import_or_stub('re') - findall = import_or_stub('re', 'findall') + datetime = import_or_stub('datetime') + datetime_type = import_or_stub('datetime', 'datetime') with pytest.raises(RuntimeError): non_existing_module.f() with pytest.raises(RuntimeError): non_existing_function() - re.findall('aba', 'a') - findall('aba', 'a') + datetime.datetime.now() + datetime_type.now() # type: ignore[union-attr] def test_conditional_import_of_multiple_functions() -> None: From 7da30afed5bde460f59aa18fc3d011370492ebd1 Mon Sep 17 00:00:00 2001 From: David Neumann Date: Tue, 3 Jun 2025 15:49:11 +0200 Subject: [PATCH 16/19] Removed Node.js From the Deployment Workflow The GitHub Actions Workflow for the deployment of CoRelAy to PyPI was copied over from ViRelAy and still contained the installation of Node.js, which is needed in ViRelAy for the building of the frontend, but is not required for the building of CoRelAy. For this reason, the installation of Node.js was removed from the GitHub Actions Workflow for the deployment. --- .github/workflows/deploy.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index dfeace7..9761185 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -48,10 +48,6 @@ jobs: version: 0.6.13 - name: Install Python run: uv python install 3.13.2 - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: '22.14.0' - name: Install CoRelAy and its Dependencies run: uv --directory source/backend sync - name: Build the CoRelAy Project From ff614752a5e435e19513ada2c4aef35ec8eac25d Mon Sep 17 00:00:00 2001 From: chrstphr Date: Tue, 8 Jul 2025 15:23:10 +0900 Subject: [PATCH 17/19] Move pyproject.toml to root - this is so that one can easily install the package with a github link - rename source -> src as per python naming convention - make python version less restrictive (3.11.12 was released April 2025) - fix github actions deploy.yaml, which was copied over from virelay without corelay-specific changes - update directory in github actions tests.yaml to reflect the top-level pyproject.toml (by removing the --directory options) - replace "PyLint Linter" for other linting stages with more descriptive names - fix typing issues: - `corelay.processor.affinity.SparseKNN`: when `symmetric=True`, the addition of the affinity matrix and its transpose results in the matrix being converted to a `bsr_sparse` matrix (from `csr_sparse), which mypy did not like, so the affinity is now "Any", which is also the result type of the function - `corelay.processor.embedding.EigenDecomposition`: the type `which` of `eigsh` was incorrect - relabel upcoming release from 0.3.0 to 1.0.0 --- .github/workflows/deploy.yaml | 8 +-- .github/workflows/tests.yml | 28 ++++---- source/.python-versions => .python-versions | 0 CHANGELOG.md | 18 +++-- docs/source/getting-started/basic-usage.rst | 2 +- docs/source/index.rst | 2 +- docs/source/migration-guide/index.rst | 2 +- ....3.rst => migrating-from-v0.2-to-v1.0.rst} | 4 +- source/pyproject.toml => pyproject.toml | 13 ++-- {source => src}/corelay/__init__.py | 0 {source => src}/corelay/base.py | 0 {source => src}/corelay/io/__init__.py | 0 {source => src}/corelay/io/hashing.py | 0 {source => src}/corelay/io/storage.py | 0 {source => src}/corelay/pipeline/__init__.py | 0 {source => src}/corelay/pipeline/base.py | 0 {source => src}/corelay/pipeline/spectral.py | 0 {source => src}/corelay/plugboard.py | 0 {source => src}/corelay/processor/__init__.py | 0 {source => src}/corelay/processor/affinity.py | 2 +- {source => src}/corelay/processor/base.py | 0 .../corelay/processor/clustering.py | 0 {source => src}/corelay/processor/distance.py | 0 .../corelay/processor/embedding.py | 2 +- {source => src}/corelay/processor/flow.py | 0 .../corelay/processor/laplacian.py | 0 .../corelay/processor/preprocessing.py | 0 {source => src}/corelay/py.typed | 0 {source => src}/corelay/tracker.py | 0 {source => src}/corelay/utils.py | 0 tests/unit_tests/.coveragerc | 6 +- tests/unit_tests/.pytest.ini | 2 +- source/tox.ini => tox.ini | 67 ++++++++----------- source/uv.lock => uv.lock | 48 ++++++++++++- 34 files changed, 119 insertions(+), 85 deletions(-) rename source/.python-versions => .python-versions (100%) rename docs/source/migration-guide/{migrating-from-v0.2-to-v0.3.rst => migrating-from-v0.2-to-v1.0.rst} (98%) rename source/pyproject.toml => pyproject.toml (97%) rename {source => src}/corelay/__init__.py (100%) rename {source => src}/corelay/base.py (100%) rename {source => src}/corelay/io/__init__.py (100%) rename {source => src}/corelay/io/hashing.py (100%) rename {source => src}/corelay/io/storage.py (100%) rename {source => src}/corelay/pipeline/__init__.py (100%) rename {source => src}/corelay/pipeline/base.py (100%) rename {source => src}/corelay/pipeline/spectral.py (100%) rename {source => src}/corelay/plugboard.py (100%) rename {source => src}/corelay/processor/__init__.py (100%) rename {source => src}/corelay/processor/affinity.py (97%) rename {source => src}/corelay/processor/base.py (100%) rename {source => src}/corelay/processor/clustering.py (100%) rename {source => src}/corelay/processor/distance.py (100%) rename {source => src}/corelay/processor/embedding.py (99%) rename {source => src}/corelay/processor/flow.py (100%) rename {source => src}/corelay/processor/laplacian.py (100%) rename {source => src}/corelay/processor/preprocessing.py (100%) rename {source => src}/corelay/py.typed (100%) rename {source => src}/corelay/tracker.py (100%) rename {source => src}/corelay/utils.py (100%) rename source/tox.ini => tox.ini (61%) rename source/uv.lock => uv.lock (95%) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 9761185..da72075 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -34,7 +34,7 @@ jobs: # Specifies that we want to deploy to PyPI environment: name: pypi - url: https://pypi.org/p/virelay + url: https://pypi.org/p/corelay # The job contains several steps: 1) checking out the repository, 2) installing the Python project management tool uv, 3) installing Python, 4) # installing the dependencies of CoRelAy, 5) building the CoRelAy project, and 6) publishing the CoRelAy project @@ -49,8 +49,8 @@ jobs: - name: Install Python run: uv python install 3.13.2 - name: Install CoRelAy and its Dependencies - run: uv --directory source/backend sync + run: uv sync - name: Build the CoRelAy Project - run: uv --directory source/backend build + run: uv build - name: Publish the CoRelAy Project to PyPI - run: uv --directory source/backend publish + run: uv publish diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ef8336d..7f300a8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,9 +47,9 @@ jobs: - name: Install Python run: uv python install ${{ matrix.python-version }} - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --dev + run: uv sync --dev - name: Run Unit Tests - run: uv --directory source run tox run -e ${{ matrix.tox-environment }} + run: uv run tox run -e ${{ matrix.tox-environment }} # Runs the PyLint linter pylint: @@ -71,9 +71,9 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --dev + run: uv sync --dev - name: Run PyLint Linter - run: uv --directory source run tox run -e pylint + run: uv run tox run -e pylint # Runs the PyCodeStyle linter pycodestyle: @@ -95,9 +95,9 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --dev - - name: Run PyLint Linter - run: uv --directory source run tox run -e pycodestyle + run: uv sync --dev + - name: Run PyCodeStyle + run: uv run tox run -e pycodestyle # Runs the PyDocLint docstring linter pydoclint: @@ -119,9 +119,9 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --dev - - name: Run PyLint Linter - run: uv --directory source run tox run -e pydoclint + run: uv sync --dev + - name: Run PyDocLint + run: uv run tox run -e pydoclint # Runs the MyPy static type checker mypy: @@ -143,9 +143,9 @@ jobs: - name: Install Python run: uv python install 3.13.3 - name: Install CoRelAy and its Dependencies - run: uv --directory source sync --dev - - name: Run PyLint Linter - run: uv --directory source run tox run -e mypy + run: uv sync --dev + - name: Run MyPy + run: uv run tox run -e mypy # Runs the Markdownlint linter markdownlint: @@ -220,4 +220,4 @@ jobs: - name: Install TeX Live for Pybtex run: sudo apt-get update -y && sudo apt-get install -y texlive texlive-latex-extra dvipng - name: Build Documentation - run: uv --directory source run tox run --notest -e docs + run: uv run tox run --notest -e docs diff --git a/source/.python-versions b/.python-versions similarity index 100% rename from source/.python-versions rename to .python-versions diff --git a/CHANGELOG.md b/CHANGELOG.md index b14f3bd..bb666c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog -## v0.3.0 +## v1.0.0 *Release date to be determined.* -### General Updates in v0.3.0 +### General Updates in v1.0.0 - Renamed the `master` branch to `main` in order to avoid any links to sensitive topics. All references to the `master` branch in the repository were updated to `main`. - Added this changelog, as well as a contributors list, which contains a list of all people that made contributions to the project. @@ -20,11 +20,10 @@ - The project is dual-licensed under the GNU General Public License Version 3 (GPL-3.0) or later, and the GNU Lesser General Public License Version 3 (LGPL-3.0) or later. The GPL-3.0 license is in the `COPYING` file and the LGPL-3.0 license is in the `COPYING.LESSER` file. Additionally, there used to be a `LICENSE` file, which contained a note about the dual-licensing. This was, however, confusing, as GitHub does not recognize that the file is only a note about the dual-licensing and not the actual license. The `LICENSE` file was removed and the note about the dual-licensing was added to the read me. - Added a `CITATION.cff` file, which contains the necessary information to cite this repository. This file is based on the [Citation File Format (CFF)](https://citation-file-format.github.io) standard. This file is supported by GitHub and results in a "Cite this repository" button on the website, which allows users to directly generate a proper citation for the repository in multiple different formats. -### CoRelAy Updates in v0.3.0 +### CoRelAy Updates in v1.0.0 - Converted the CoRelAy project from a `setup.py` project to a uv project: - A `pyproject.toml` file was created, which is configured to do the same as the `setup.py` file. - - The source code was moved from `src/corelay` to `source/corelay`. - The tox configuration was updated and now uses tox-uv to run all commands via uv instead of directly creating environments. This means, that all Python environments can now be run without having to install multiple Python versions. - The tox configuration was also cleaned up. - Support for Python 3.7, 3.8, and 3.9 were removed. Python 3.7 and 3.8 have already reached their end-of-life, and Python 3.9 is about to reach its end-of-life and already only receives security updates. Besides their support status, some of the dependencies (especially tox-uv) do not support them anymore. Even Python 3.10 is already unsupported by some of the dependencies. For this reason, only Python 3.11, 3.12, and 3.13 are supported now. They are recorded in the `.python-versions` file, which makes it trivial to install them using uv. @@ -52,9 +51,8 @@ - Variables were renamed to be more descriptive. Previously, most variable names were heavily abbreviated and some of them were not very intuitive. - Most of the inline PyLint disables were removed and either the offending rule was directly disabled or the code was changed to not trigger the rule anymore. This was done to make the code cleaner and easier to read. For all remaining inline PyLint disables, the reason for the disable was better explained. - Relative imports were replaced by absolute imports, because they make it easier to understand where the module is located. Especially because the project has multiple modules that are named the same in different sub-packages. - - Type hints were added to all functions, methods, class attributes, and variables where necessary. Also, parameters and attributes of type string that had a specific set of possible values are now typed using the `Literal` type. This makes possible for MyPy to check if specified values are valid. + - Type hints were added to all functions, methods, class attributes, and variables where necessary. Also, parameters and attributes of type string that had a specific set of possible values are now typed using the `Literal` type. This makes it possible for MyPy to check if specified values are valid. - In the unit tests, the fixture parameters were masking the fixture function names. This was fixed by renaming the fixture function names `get__fixture` and specifying an explicit fixture name. The fixtures are now also explicitly scoped to the module level, although this did not cause any problems previously. -- The tox configuration was moved from the root directory to the `source` directory, which is more appropriate, as tox is mostly used for building, testing, and linting the project and should therefore be in the same directory as the rest of the project files. Also, since tox now uses uv, it needs to be in the same directory as the `pyproject.toml` file in order to be able to install dependencies. - The `corelay/version.py` file was deleted as it is automatically generated during the build process and should not be checked into source control. - In general, the code was cleaned up to make it easier to read, understand, and maintain. Some instances of dead code were eliminated. The goal was to make the code more Pythonic and to follow the PEP 8 style guide as closely as possible, while still maintaining backwards compatibility. This was, however, not possible in all cases and some backwards compatibility was sacrificed for the sake of improved static typing. This includes: - Some meta classes were removed and replaced by protocols (which are implicit interfaces). @@ -72,7 +70,7 @@ - Fixed a bug in the `Histogram` processor: The processor was using the `numpy.histogramdd` function, which computes a multivariate histogram, but the processor was meant to compute a histogram over the channels of the input data, for which the `numpy.histogramdd` function is not suitable. Instead, the `numpy.histogram` function is now used in conjunction with the `numpy.stack` function to compute the histogram over the channels of the input data. Also, the `Histogram` processor was not able to deal with channel-last data, which is now supported. The `Histogram` processor created in the `virelay_analysis.py` example script was also not working as expected: Although it was using the `numpy.histogram` function, it did not account for its return type, which is a tuple containing the histogram and the bin edges. Since we now have a working implementation of the `Histogram` processor, the example script was updated to use it. - The `Shaper` flow processor was extended to support dictionaries and string indices. It seems like, these features were already expected to be present, but they were prevented by some minor mistakes: Dictionaries were not supported, because the `Shaper` processor tested for the type of the input data by testing if they implemented the `Sequence` protocol, which is not the case for dictionaries. Instead, dictionaries implement the `Mapping` protocol, which is now also tested for. String indices were not supported, because the indices were tested for implementing the `Sequence` protocol, which is also the case for strings. This meant, that each character of a string was treated as a separate index, which is not the intended behavior. Now, it is also checked if the indices are not strings. -### CI/CD Updates in v0.3.0 +### CI/CD Updates in v1.0.0 - The GitHub Actions workflow was updated to point to the new locations of the source code, unit tests, and configuration files. - Converted the GitHub Actions workflow to use uv to run the tests, linters, and build the documentation. @@ -85,7 +83,7 @@ - Added a new GitHub Actions workflow, which builds the project and publishes it to PyPI. This workflow is triggered when GitHub release for a new version is created. - The configuration for the GitLab CI, which was stored in the `.gitlab-ci.yml` file, was removed. The project is no longer being hosted on GitLab, and the CI configuration is no longer needed. -### Documentation Updates in v0.3.0 +### Documentation Updates in v1.0.0 - The documentation was largely extended and a bit reorganized: - The API reference documentation was moved from the `reference` directory to the `api-reference` directory. This was done to better reflect the purpose of the documentation, since there are also bibliographical references in the documentation. @@ -93,7 +91,7 @@ - The installation and basic usage sections were moved from the original "Getting Started" section. - The "Example Project" section contains a more elaborate example of how to use CoRelAy to analyze a dataset generated by Zennit using the SpRAy workflow that can be visualized using ViRelAy. - A new "Contributor's Guide" section was added with sub-sections on how to report issues and feature requests, and how to contribute code or documentation. - - A new "Migration Guide" section was added to help users migrate from CoRelAy v0.2 to CoRelAy v0.3. This was done, because the changes made in CoRelAy v0.3 are quite extensive and it is not easy to find out what has changed and how to adapt existing code to the new version. + - A new "Migration Guide" section was added to help users migrate from CoRelAy v0.2 to CoRelAy v1.0. This was done, because the changes made in CoRelAy v1.0 are quite extensive and it is not easy to find out what has changed and how to adapt existing code to the new version. - More citations were added to the documentation to relevant literature. - The configuration for Read the Docs was updated to use the latest available versions of Ubuntu (24.04) and Python (3.12). It was also documented. - The configuration for Sphinx was updated: @@ -104,7 +102,7 @@ - The `docs` environment in the tox configuration now uses Python 3.13.3, which causes an error during the building of the documentation, because the `pkg_resources` module was deprecated and removed in Python 3.12. This module was used to get the source code file paths and line numbers of functions, classes, methods, etc. for the `linkcode` extension. This was replaced by the `inspect` module. - The invocation of Sphinx in tox to build the documentation was updated to include the `--fresh-env` option, so that the documentation is always built in a fresh environment. This solves some issues with the documentation build, where the documentation was not updated correctly after changes were made to the source code or the documentation itself. - The correct capitalization of the project name is now specified in the Sphinx configuration and the year in the copyright is now always set to the current year instead of hard-coding it. -- The example scripts were updated to reflect the coding style and docstring conventions used in the CoRelAy library. They were moved from the `example` directory to the `docs/examples` directory. This was done because the examples are part part of the documentation and to keep the root directory clean and free of unnecessary clutter. +- The example scripts were updated to reflect the coding style and docstring conventions used in the CoRelAy library. They were moved from the `example` directory to the `docs/examples` directory. This was done because the examples are part of the documentation and to keep the root directory clean and free of unnecessary clutter. - A custom CSS file was added to the documentation to change the alignment of the documentation text to be justified and to automatically break long words. This was done to improve the readability of the documentation and to align it with usual scientific documents. - The width of the labels in the bibliography was set to a fixed size, so that the labels all have the same width. This was done, because they looked inconsistent and messy before. - The logo was added to the documentation, which previously contained a copy of the logo in the `docs/images` directory, but did not include it. The version contained in the `docs/images` directory was removed and the index page now directly references the logo in the `design` directory. diff --git a/docs/source/getting-started/basic-usage.rst b/docs/source/getting-started/basic-usage.rst index 9cf4782..aaccf2b 100644 --- a/docs/source/getting-started/basic-usage.rst +++ b/docs/source/getting-started/basic-usage.rst @@ -19,7 +19,7 @@ In CoRelAy, a **Processor** is defined by sub-classing :py:class:`~corelay.proce .. note:: - If you come from a version of CoRelAy before 0.3.0, you may be used to the old syntax of registering parameters by assigning an instance of :py:class:`~corelay.base.Param` to a class attribute. For more information on this change and how to migrate, please refer to the :doc:`migration guide <../migration-guide/migrating-from-v0.2-to-v0.3>`. + If you come from a version of CoRelAy before 1.0.0, you may be used to the old syntax of registering parameters by assigning an instance of :py:class:`~corelay.base.Param` to a class attribute. For more information on this change and how to migrate, please refer to the :doc:`migration guide <../migration-guide/migrating-from-v0.2-to-v1.0>`. The processor will automatically track the parameters and allows users to set them in the constructor using the attribute's name as a keyword argument. Parameters can, however, also be made into positional arguments by setting the ``is_positional`` argument of :py:meth:`Param.__init__ ` to :py:obj:`True`. This allows for a more flexible and user-friendly interface when creating custom processors. The parameters can be accessed as attributes of the processor instance. Invoking a processor to perform the associated operation is as easy as calling it like a function. diff --git a/docs/source/index.rst b/docs/source/index.rst index a2e5b71..e8a04ec 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -13,7 +13,7 @@ Tasks in CoRelAy are highly flexible and can be tailored to meet the needs of yo .. note:: - If you come from a previous version of CoRelAy before the 0.3.0 release, please refer to the :doc:`migration guide ` for information on how to transition to the latest version. Some breaking changes have been introduced, and the migration guide will help you adapt your existing code to the new version. + If you come from a previous version of CoRelAy before the 1.0.0 release, please refer to the :doc:`migration guide ` for information on how to transition to the latest version. Some breaking changes have been introduced, and the migration guide will help you adapt your existing code to the new version. Contents ======== diff --git a/docs/source/migration-guide/index.rst b/docs/source/migration-guide/index.rst index 82b0633..4699ce0 100644 --- a/docs/source/migration-guide/index.rst +++ b/docs/source/migration-guide/index.rst @@ -7,4 +7,4 @@ This migration guide provides information on how to transition from older versio .. toctree:: :maxdepth: 2 - migrating-from-v0.2-to-v0.3 + migrating-from-v0.2-to-v1.0 diff --git a/docs/source/migration-guide/migrating-from-v0.2-to-v0.3.rst b/docs/source/migration-guide/migrating-from-v0.2-to-v1.0.rst similarity index 98% rename from docs/source/migration-guide/migrating-from-v0.2-to-v0.3.rst rename to docs/source/migration-guide/migrating-from-v0.2-to-v1.0.rst index d1d6da6..217e406 100644 --- a/docs/source/migration-guide/migrating-from-v0.2-to-v0.3.rst +++ b/docs/source/migration-guide/migrating-from-v0.2-to-v1.0.rst @@ -1,8 +1,8 @@ =============================== -Migrating from v0.2.* to v0.3.* +Migrating from v0.2.* to v1.0.* =============================== -Between v0.2.* and v0.3.*, CoRelAy has made some significant changes, including, but not limited to, the following: +Between v0.2.* and v1.0.*, CoRelAy has made some significant changes, including, but not limited to, the following: * The project was converted from a ``setup.py`` project to a ``uv`` project. * The Python dependencies were updated to the latest versions. diff --git a/source/pyproject.toml b/pyproject.toml similarity index 97% rename from source/pyproject.toml rename to pyproject.toml index 86b8775..3610259 100644 --- a/source/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ keywords = [ "Artificial Intelligence" ] license = "GPL-3.0-or-later AND LGPL-3.0-or-later" -requires-python = ">=3.11.12" +requires-python = ">=3.11.0" dynamic = [ "readme", "version" @@ -151,6 +151,12 @@ requires = [ ] build-backend = "hatchling.build" +[tool.hatch.build] +include = ["src"] + +[tool.hatch.build.targets.wheel] +packages = ["src/corelay"] + # Allows Hatch to install packages from Git repositories [tool.hatch.metadata] allow-direct-references = true @@ -159,15 +165,14 @@ allow-direct-references = true [tool.hatch.version] source = "vcs" fallback-version = "0.0.0" -raw-options = { root = ".." } # Configures the Hatch-VCS build hook, which is used to write the version of the project to the version file [tool.hatch.build.hooks.vcs] -version-file = "corelay/version.py" +version-file = "src/corelay/version.py" # Configures the Hatch Fancy PyPI Readme plugin to compose the content of the read me file from the project's read me file, which is located in the # root directory of the repository [tool.hatch.metadata.hooks.fancy-pypi-readme] content-type = "text/markdown" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] -path = "../README.md" +path = "README.md" diff --git a/source/corelay/__init__.py b/src/corelay/__init__.py similarity index 100% rename from source/corelay/__init__.py rename to src/corelay/__init__.py diff --git a/source/corelay/base.py b/src/corelay/base.py similarity index 100% rename from source/corelay/base.py rename to src/corelay/base.py diff --git a/source/corelay/io/__init__.py b/src/corelay/io/__init__.py similarity index 100% rename from source/corelay/io/__init__.py rename to src/corelay/io/__init__.py diff --git a/source/corelay/io/hashing.py b/src/corelay/io/hashing.py similarity index 100% rename from source/corelay/io/hashing.py rename to src/corelay/io/hashing.py diff --git a/source/corelay/io/storage.py b/src/corelay/io/storage.py similarity index 100% rename from source/corelay/io/storage.py rename to src/corelay/io/storage.py diff --git a/source/corelay/pipeline/__init__.py b/src/corelay/pipeline/__init__.py similarity index 100% rename from source/corelay/pipeline/__init__.py rename to src/corelay/pipeline/__init__.py diff --git a/source/corelay/pipeline/base.py b/src/corelay/pipeline/base.py similarity index 100% rename from source/corelay/pipeline/base.py rename to src/corelay/pipeline/base.py diff --git a/source/corelay/pipeline/spectral.py b/src/corelay/pipeline/spectral.py similarity index 100% rename from source/corelay/pipeline/spectral.py rename to src/corelay/pipeline/spectral.py diff --git a/source/corelay/plugboard.py b/src/corelay/plugboard.py similarity index 100% rename from source/corelay/plugboard.py rename to src/corelay/plugboard.py diff --git a/source/corelay/processor/__init__.py b/src/corelay/processor/__init__.py similarity index 100% rename from source/corelay/processor/__init__.py rename to src/corelay/processor/__init__.py diff --git a/source/corelay/processor/affinity.py b/src/corelay/processor/affinity.py similarity index 97% rename from source/corelay/processor/affinity.py rename to src/corelay/processor/affinity.py index c8fd3d3..219baca 100644 --- a/source/corelay/processor/affinity.py +++ b/src/corelay/processor/affinity.py @@ -72,7 +72,7 @@ def function(self, data: typing.Any) -> typing.Any: # Denotes the existing edges with ones values = numpy.ones((number_of_samples, number_of_neighbors), dtype=data.dtype) - affinity = scipy.sparse.csr_matrix((values.flat, (rows.flat, columns.flat)), shape=(number_of_samples, number_of_samples)) + affinity: typing.Any = scipy.sparse.csr_matrix((values.flat, (rows.flat, columns.flat)), shape=(number_of_samples, number_of_samples)) # Makes the affinity matrix symmetric if self.symmetric: diff --git a/source/corelay/processor/base.py b/src/corelay/processor/base.py similarity index 100% rename from source/corelay/processor/base.py rename to src/corelay/processor/base.py diff --git a/source/corelay/processor/clustering.py b/src/corelay/processor/clustering.py similarity index 100% rename from source/corelay/processor/clustering.py rename to src/corelay/processor/clustering.py diff --git a/source/corelay/processor/distance.py b/src/corelay/processor/distance.py similarity index 100% rename from source/corelay/processor/distance.py rename to src/corelay/processor/distance.py diff --git a/source/corelay/processor/embedding.py b/src/corelay/processor/embedding.py similarity index 99% rename from source/corelay/processor/embedding.py rename to src/corelay/processor/embedding.py index 9c40d23..5c0b64c 100644 --- a/source/corelay/processor/embedding.py +++ b/src/corelay/processor/embedding.py @@ -115,7 +115,7 @@ def function(self, data: typing.Any) -> typing.Any: # This is necessary to ensure that MyPy does not complain that the "which" argument is not valid; ideally, we would use literals ourselves, # but unfortunately, Sphinx AutoDoc cannot handle type aliases correctly unless we use Postponed Evaluation of Annotations (PEP 563), which in # turn breaks our usage of typing.Annotated for slots - EigenvalueAndEigenvectorType: TypeAlias = Literal['LM', 'SM', 'LR', 'SR', 'LI', 'SI'] + EigenvalueAndEigenvectorType: TypeAlias = Literal['LM', 'SM', 'LA', 'SA', 'BE'] eigenvalue_and_eigenvector_types = list(get_args(EigenvalueAndEigenvectorType)) def check_if_eigenvalue_and_eigenvector_type_is_valid(eigenvalue_and_eigenvector_type: str) -> TypeGuard[EigenvalueAndEigenvectorType]: diff --git a/source/corelay/processor/flow.py b/src/corelay/processor/flow.py similarity index 100% rename from source/corelay/processor/flow.py rename to src/corelay/processor/flow.py diff --git a/source/corelay/processor/laplacian.py b/src/corelay/processor/laplacian.py similarity index 100% rename from source/corelay/processor/laplacian.py rename to src/corelay/processor/laplacian.py diff --git a/source/corelay/processor/preprocessing.py b/src/corelay/processor/preprocessing.py similarity index 100% rename from source/corelay/processor/preprocessing.py rename to src/corelay/processor/preprocessing.py diff --git a/source/corelay/py.typed b/src/corelay/py.typed similarity index 100% rename from source/corelay/py.typed rename to src/corelay/py.typed diff --git a/source/corelay/tracker.py b/src/corelay/tracker.py similarity index 100% rename from source/corelay/tracker.py rename to src/corelay/tracker.py diff --git a/source/corelay/utils.py b/src/corelay/utils.py similarity index 100% rename from source/corelay/utils.py rename to src/corelay/utils.py diff --git a/tests/unit_tests/.coveragerc b/tests/unit_tests/.coveragerc index 69c23c7..5b1e3fd 100644 --- a/tests/unit_tests/.coveragerc +++ b/tests/unit_tests/.coveragerc @@ -27,8 +27,8 @@ exclude_also = raise NotImplementedError # Since the coverage data can be collected from multiple different installations of CoRelAy, the Coverage tool needs to know which files are -# equivalent; this configuration section contains named lists (in our case only a single list called "source"), where two file paths are considered +# equivalent; this configuration section contains named lists (in our case only a single list called "src"), where two file paths are considered # equivalent and combined when running the "coverage combine" command when they are in the same list; here we specify that the files in directories -# called */source/corelay and */.tox/*/lib/python*/site-packages/corelay are equivalent +# called */src/corelay and */.tox/*/lib/python*/site-packages/corelay are equivalent [paths] -source = */source/corelay,*/.tox/*/lib/python*/site-packages/corelay +source = */src/corelay,*/.tox/*/lib/python*/site-packages/corelay diff --git a/tests/unit_tests/.pytest.ini b/tests/unit_tests/.pytest.ini index dd79e01..0e651b3 100644 --- a/tests/unit_tests/.pytest.ini +++ b/tests/unit_tests/.pytest.ini @@ -3,7 +3,7 @@ [pytest] # Specifies the directories that PyTest should search for test files when no files or directories are specified on the command line -testpaths = ../tests/unit_tests +testpaths = tests/unit_tests # Specifies extra command line arguments that should be passed to PyTest when it is run # -ra: Shows extra test summary info as specified by characters: (f) failed, (E) error, (s) skipped, (x) failed, (X) passed, (p) passed, (P) passed diff --git a/source/tox.ini b/tox.ini similarity index 61% rename from source/tox.ini rename to tox.ini index de1ac13..ce61c5d 100644 --- a/source/tox.ini +++ b/tox.ini @@ -20,32 +20,32 @@ envlist = uv_python_preference = only-managed dependency_groups = testing setenv = - COVERAGE_FILE = ../tests/unit_tests/coverage/.coverage.{envname} + COVERAGE_FILE = tests/unit_tests/coverage/.coverage.{envname} commands = python -m pytest \ - --config-file "../tests/unit_tests/.pytest.ini" \ + --config-file "tests/unit_tests/.pytest.ini" \ --cov corelay \ - --cov-config "../tests/unit_tests/.coveragerc" \ + --cov-config "tests/unit_tests/.coveragerc" \ --cov-append \ - {posargs:../tests/unit_tests} + {posargs:tests/unit_tests} # A test environment that combines the coverage data from all runs of the unit tests with the different Python versions and generates a single report [testenv:coverage] setenv = - COVERAGE_FILE = ../tests/unit_tests/coverage/.coverage + COVERAGE_FILE = tests/unit_tests/coverage/.coverage depends = py311 py312 py313 commands = coverage combine \ - --rcfile "../tests/unit_tests/.coveragerc" + --rcfile "tests/unit_tests/.coveragerc" coverage report \ - --rcfile "../tests/unit_tests/.coveragerc" \ + --rcfile "tests/unit_tests/.coveragerc" \ --show-missing coverage html \ - --rcfile "../tests/unit_tests/.coveragerc" \ - --directory "../tests/unit_tests/coverage/report" + --rcfile "tests/unit_tests/.coveragerc" \ + --directory "tests/unit_tests/coverage/report" # A test environment that will build the documentation using Sphinx [testenv:docs] @@ -57,10 +57,10 @@ commands = --fail-on-warning \ --fresh-env \ --keep-going \ - --doctree-dir "../docs/doctree" \ + --doctree-dir "docs/doctree" \ --builder html \ - "../docs/source" \ - "../docs/build" \ + "docs/source" \ + "docs/build" \ {posargs} # A test environment that will run the PyLint linter on the CoRelAy package, the unit tests, the Sphinx configuration file, and the examples; PyLint @@ -75,12 +75,12 @@ dependency_groups = docs commands = pylint \ - --rcfile="../tests/linters/.pylintrc" \ + --rcfile="tests/linters/.pylintrc" \ --output-format="parseable" \ - corelay \ - "../tests/unit_tests" \ - "../docs/source/conf.py" \ - "../docs/examples" + src/corelay \ + "tests/unit_tests" \ + "docs/source/conf.py" \ + "docs/examples" # A test environment that will run the PyCodeStyle linter on the REST API backend, the unit tests, the setup script, and the Sphinx configuration file [testenv:pycodestyle] @@ -88,24 +88,11 @@ base_python = py313 dependency_groups = linting commands = pycodestyle \ - --config="../tests/linters/.pycodestyle" \ - corelay \ - "../tests/unit_tests" \ - "../docs/source/conf.py" \ - "../docs/examples" - -# A test environment that will run the PyDocLint docstring linter on the REST API backend, the unit tests, the setup script, and the Sphinx -# configuration file -[testenv:pydoclint] -base_python = py313 -dependency_groups = linting -commands = - pydoclint \ - --config="../tests/linters/.pydoclint.toml" \ - corelay \ - "../tests/unit_tests" \ - "../docs/source/conf.py" \ - "../docs/examples" + --config="tests/linters/.pycodestyle" \ + src/corelay \ + "tests/unit_tests" \ + "docs/source/conf.py" \ + "docs/examples" # A test environment that will run the MyPy static type checker on the REST API backend, the unit tests, the setup script, and the Sphinx # configuration file @@ -114,8 +101,8 @@ base_python = py313 dependency_groups = linting commands = mypy \ - --config-file="../tests/linters/.mypy.ini" \ - corelay \ - "../tests/unit_tests" \ - "../docs/source/conf.py" \ - "../docs/examples" + --config-file="tests/linters/.mypy.ini" \ + src/corelay \ + "tests/unit_tests" \ + "docs/source/conf.py" \ + "docs/examples" diff --git a/source/uv.lock b/uv.lock similarity index 95% rename from source/uv.lock rename to uv.lock index c6c6266..debf877 100644 --- a/source/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = ">=3.11.12" +requires-python = ">=3.11.0" resolution-markers = [ "python_full_version >= '3.12'", "python_full_version < '3.12'", @@ -380,6 +380,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" }, ] +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -1168,7 +1173,7 @@ name = "pytest-cov" version = "6.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage" }, + { name = "coverage", extra = ["toml"] }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } @@ -1598,6 +1603,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/be/10d23cfd4078fbec6aba768a357eff9e70c0b6d2a07398425985c524ad2a/tifffile-2025.3.30-py3-none-any.whl", hash = "sha256:0ed6eee7b66771db2d1bfc42262a51b01887505d35539daef118f4ff8c0f629c", size = 226837, upload-time = "2025-03-30T04:45:29Z" }, ] +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + [[package]] name = "tomlkit" version = "0.13.2" From 0bfc685aeec9df9359a52246ae77ab8c67ac5be6 Mon Sep 17 00:00:00 2001 From: chrstphr Date: Mon, 21 Jul 2025 16:39:25 +0900 Subject: [PATCH 18/19] Update .readthedocs.yaml for use with uv --- .readthedocs.yaml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 2b71c6a..212fb60 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,22 +1,17 @@ - -# Use the v2 version of the Read the Docs build configuration file format version: 2 -# Configures the build environment to use Ubuntu 24.04 and Python 3.12 build: - os: ubuntu-24.04 + os: ubuntu-lts-latest tools: python: "3.13" + jobs: + create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --group docs + install: + - "true" -# Configures the Sphinx documentation build to use the configuration file located at docs/source/conf.py sphinx: configuration: docs/source/conf.py - -# Configures the Python environment to be used; the documentation uses the linkcode extension, to generate links to the source code on GitHub; to -# produce the links, the extension needs ViRelAy to be installed; also, the "docs" extra requirements are installed, which contains the Sphinx -# extensions used in the documentation -python: - install: - - method: pip - path: . - extra_requirements: ["docs"] From 924c7fcc4fcf776b543be3881321542ab5eb7990 Mon Sep 17 00:00:00 2001 From: chrstphr Date: Mon, 21 Jul 2025 16:59:05 +0900 Subject: [PATCH 19/19] Changelog: Add release date for v1.0.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb666c9..462eccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v1.0.0 -*Release date to be determined.* +*Released on July 21, 2025.* ### General Updates in v1.0.0

XEJECFm?25}Sj{Ij&5~w`ogot#_E-ty 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 z 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