├── init.py ├── tests ├── __init__.py ├── conftest.py ├── input_data │ ├── default_plot.png │ ├── plot_options.png │ ├── plot_three.png │ ├── df_styled_num_fmt.html │ ├── df_styled.html │ └── df_labeled.html ├── test_df.py ├── base.py └── test_sensitivity_analyzer.py ├── examples ├── __init__.py └── README.rst ├── docsrc ├── directives │ ├── __init__.py │ └── auto_summary.py ├── download-logo.sh ├── .gitignore ├── nb-examples.sh ├── binder_requirements.sh ├── source │ ├── tutorial.rst │ ├── _static │ │ └── custom.css │ ├── index.rst │ ├── overview.rst │ └── conf.py ├── make.bat ├── Makefile └── apidoc │ └── templates │ └── package.rst_t ├── env.sh ├── MANIFEST.in ├── codecov.yml ├── name.py ├── directory.py ├── mypy.ini ├── binder_requirements.py ├── sensitivity ├── colors.py ├── __init__.py ├── _ignore_warn.py ├── hexbin.py ├── df.py └── main.py ├── output-version.sh ├── version.py ├── .github ├── labels.yml ├── workflows │ ├── sync-labels.yml │ ├── docs.yml │ ├── template-update.yml │ ├── package.yml │ └── automerge.yml └── release-drafter.yml ├── is_maintainer.py ├── Pipfile ├── .cruft.json ├── get_logo.py ├── scripts └── workflows-updated.sh ├── upload.py ├── .gitignore ├── LICENSE.md ├── setup.py ├── README.md ├── nbexamples └── ipynb_to_gallery.py ├── conf.py └── Pipfile.lock /init.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docsrc/directives/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | eval "$(python conf.py)"; -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include conf.py 2 | include version.py -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | 2 | ignore: 3 | - "*" 4 | - "tests/**" 5 | - "examples/**" -------------------------------------------------------------------------------- /docsrc/download-logo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | python get_logo.py 4 | cd docsrc 5 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | pytest.register_assert_rewrite('tests.base') -------------------------------------------------------------------------------- /name.py: -------------------------------------------------------------------------------- 1 | 2 | if __name__ == '__main__': 3 | import conf 4 | print(conf.PACKAGE_NAME) 5 | -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | Example Usage 2 | ================== 3 | 4 | See below for some examples of usage. -------------------------------------------------------------------------------- /directory.py: -------------------------------------------------------------------------------- 1 | 2 | if __name__ == '__main__': 3 | import conf 4 | print(conf.PACKAGE_DIRECTORY) 5 | -------------------------------------------------------------------------------- /docsrc/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | source/api 3 | source/binder/requirements.txt 4 | source/_static/images/logo.svg -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | # Global options: 2 | 3 | [mypy] 4 | ignore_missing_imports = True 5 | 6 | # Per-module options: 7 | 8 | -------------------------------------------------------------------------------- /tests/input_data/default_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickderobertis/sensitivity/HEAD/tests/input_data/default_plot.png -------------------------------------------------------------------------------- /tests/input_data/plot_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickderobertis/sensitivity/HEAD/tests/input_data/plot_options.png -------------------------------------------------------------------------------- /tests/input_data/plot_three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickderobertis/sensitivity/HEAD/tests/input_data/plot_three.png -------------------------------------------------------------------------------- /binder_requirements.py: -------------------------------------------------------------------------------- 1 | import conf 2 | 3 | if __name__ == '__main__': 4 | for package in conf.BINDER_ENVIRONMENT_REQUIRES: 5 | print(package) -------------------------------------------------------------------------------- /docsrc/nb-examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | cp -R examples _examples 4 | python ./nbexamples/ipynb_to_gallery.py ./nbexamples/ --out-folder ./_examples --replace 5 | cd docsrc 6 | -------------------------------------------------------------------------------- /docsrc/binder_requirements.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | mkdir -p docsrc/source/binder/ 4 | echo "$(python binder_requirements.py)" > docsrc/source/binder/requirements.txt 5 | cd docsrc 6 | -------------------------------------------------------------------------------- /sensitivity/colors.py: -------------------------------------------------------------------------------- 1 | 2 | def _get_color_map(reverse_colors: bool = False, color_map: str = 'RdYlGn') -> str: 3 | if reverse_colors: 4 | color_map += '_r' 5 | return color_map 6 | -------------------------------------------------------------------------------- /output-version.sh: -------------------------------------------------------------------------------- 1 | version=$(python version.py); 2 | 3 | if [ $? -ne 0 ]; then 4 | echo "Error getting current build version" >&2; 5 | 6 | exit 1; 7 | fi 8 | 9 | echo ::set-output name=version::$version -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | import conf 2 | major, minor, release = conf.PACKAGE_VERSION_TUPLE 3 | __version__ = f"{major}.{minor}.{release}" 4 | __version_info__ = conf.PACKAGE_VERSION_TUPLE 5 | 6 | if __name__ == '__main__': 7 | print(__version__) -------------------------------------------------------------------------------- /sensitivity/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python sensitivity analysis - run models with varying inputs to produce 3 | visualizations including gradient DataFrames and hex-bin plots 4 | """ 5 | from sensitivity.main import SensitivityAnalyzer 6 | from sensitivity import _ignore_warn 7 | 8 | _ignore_warn.ignore_nested_library_warnings() 9 | 10 | 11 | -------------------------------------------------------------------------------- /sensitivity/_ignore_warn.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | def ignore_nested_library_warnings(): 4 | warnings.filterwarnings("ignore", module="numpy.core.fromnumeric", category=FutureWarning) 5 | warnings.filterwarnings("ignore", module="pandas.io.formats.style", category=PendingDeprecationWarning, message="The get_cmap function will be deprecated in a future version") -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | # Labels here will be synced to the repo 2 | - name: maintenance 3 | description: Changes which don't directly support a feature 4 | color: 4ec76e 5 | - name: no auto merge 6 | description: Prevent CI from automerging PR from maintainer 7 | color: edae40 8 | - name: automated issue 9 | description: Issue raised by a bot 10 | color: bf40ed 11 | - name: automated pr 12 | description: PR raised by a bot 13 | color: e622cf -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Sync labels 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - .github/labels.yml 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: micnncim/action-label-syncer@v1 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | with: 18 | manifest: .github/labels.yml 19 | prune: false 20 | -------------------------------------------------------------------------------- /is_maintainer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Script to check within Github Actions whether current actor is one of the package maintainers 3 | """ 4 | import os 5 | 6 | from conf import REPO_MAINTAINERS 7 | 8 | if __name__ == '__main__': 9 | user = os.environ['GITHUB_PR_USER'] 10 | if user in REPO_MAINTAINERS: 11 | print(f'Github PR user {user} was in maintainers, will auto merge PR') 12 | exit(0) 13 | print(f"Github PR user was {user}, not in maintainers {REPO_MAINTAINERS}, so will not auto merge PR") 14 | exit(1) 15 | -------------------------------------------------------------------------------- /docsrc/directives/auto_summary.py: -------------------------------------------------------------------------------- 1 | from sphinx.ext.autosummary import Autosummary 2 | from typing import List, Tuple 3 | 4 | 5 | class AutoSummaryNameOnly(Autosummary): 6 | 7 | def get_table(self, items: List[Tuple[str, str, str, str]]): 8 | new_items = [] 9 | for name, sig, summary, real_name in items: 10 | name_parts = name.split('.') 11 | new_name = name_parts[-1] 12 | new_items.append((new_name, sig, summary, real_name)) 13 | return super().get_table(new_items) 14 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$NEXT_PATCH_VERSION 🌈' 2 | tag-template: 'v$NEXT_PATCH_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: '🧰 Maintenance' 14 | label: 'maintenance' 15 | - title: '📖 Documentation' 16 | label: 'documentation' 17 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 18 | template: | 19 | ## Changes 20 | 21 | $CHANGES 22 | -------------------------------------------------------------------------------- /docsrc/source/tutorial.rst: -------------------------------------------------------------------------------- 1 | Getting started with sensitivity 2 | ********************************** 3 | 4 | Install 5 | ======= 6 | 7 | Install via:: 8 | 9 | pip install sensitivity 10 | 11 | Usage 12 | ========= 13 | 14 | See more in the Example Usage section. 15 | 16 | Simple usage:: 17 | 18 | from sensitivity import SensitivityAnalyzer 19 | 20 | def my_model(x_1, x_2): 21 | return x_1 ** x_2 22 | 23 | sensitivity_dict = { 24 | 'x_1': [10, 20, 30], 25 | 'x_2': [1, 2, 3] 26 | } 27 | 28 | sa = SensitivityAnalyzer(sensitivity_dict, my_model) 29 | plot = sa.plot() 30 | styled_df = sa.styled_dfs() -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | sphinx = "*" 10 | sphinx-autobuild = "*" 11 | sphinx-autodoc-typehints = "*" 12 | sphinxcontrib-fulltoc = "*" 13 | sphinx-paramlinks = "*" 14 | sphinx-rtd-theme = {editable = true,git = "https://github.com/readthedocs/sphinx_rtd_theme.git",ref = "ab7d388448258a24f8f4fa96dccb69d24f571736"} 15 | sphinx-gallery = "*" 16 | sphinx-copybutton = "*" 17 | sphinx-sitemap = "*" 18 | twine = "*" 19 | pytest = "*" 20 | pytest-cov = "*" 21 | mypy = "*" 22 | pypandoc = "*" 23 | cruft = "*" 24 | pandas = "*" 25 | matplotlib = "*" 26 | jupyter = "*" 27 | bs4 = "*" 28 | tqdm = "*" 29 | -------------------------------------------------------------------------------- /.cruft.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "https://github.com/nickderobertis/cookiecutter-pypi-sphinx", 3 | "commit": "091fcff1b2a425adc13a75fc587fbbb190b7ca3a", 4 | "context": { 5 | "cookiecutter": { 6 | "package_name": "sensitivity", 7 | "package_directory": "sensitivity", 8 | "full_name": "Python Sensitivity Analysis", 9 | "repo_name": "sensitivity", 10 | "repo_username": "nickderobertis", 11 | "short_description": "Python Sensitivity Analysis - Gradient DataFrames and Hex-Bin Plots", 12 | "package_author": "Nick DeRobertis", 13 | "author_email": "whoopnip@gmail.com", 14 | "google_analytics_id": "UA-158144725-1", 15 | "logo_url": "", 16 | "install_packages": "", 17 | "_template": "https://github.com/nickderobertis/cookiecutter-pypi-sphinx" 18 | } 19 | }, 20 | "checkout": null 21 | } 22 | -------------------------------------------------------------------------------- /docsrc/source/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* Change background color of code blocks from yellow to light gray */ 2 | .highlight { background: #f8f8f8; } 3 | 4 | 5 | /* Make tables wrap text. Once it is impossible to wrap text, then scroll. */ 6 | .wy-table-responsive table td { 7 | white-space: unset; 8 | } 9 | 10 | /* Next three styles to set to green theme */ 11 | .wy-side-nav-search { 12 | background-color: #178033; 13 | } 14 | 15 | .wy-nav-top { 16 | background: #178033; 17 | } 18 | 19 | .wy-menu-vertical header, .wy-menu-vertical p.caption { 20 | color: #3aa83e; 21 | } 22 | 23 | /* Make copy code to clipboard button bigger */ 24 | a.copybtn { 25 | width: 1.8em; 26 | height: 1.8em; 27 | } 28 | 29 | /* Make logo smaller */ 30 | img.logo.logo.logo { 31 | max-width: 50%; 32 | } 33 | /* Make logo smaller */ 34 | img.logo.logo.logo { 35 | max-width: 50%; 36 | } -------------------------------------------------------------------------------- /tests/test_df.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas.testing import assert_frame_equal 3 | 4 | from sensitivity.df import sensitivity_df 5 | from tests.base import EXPECT_DF_TWO_VALUE, SENSITIVITY_VALUES_TWO_VALUE, add_5_to_values, RESULT_NAME, \ 6 | TWO_VALUE_LABELS, EXPECT_DF_TWO_VALUE_LABELS 7 | 8 | 9 | def test_create_sensitivity_df(): 10 | df = sensitivity_df( 11 | SENSITIVITY_VALUES_TWO_VALUE, 12 | add_5_to_values, 13 | result_name=RESULT_NAME 14 | ) 15 | 16 | assert_frame_equal(df, EXPECT_DF_TWO_VALUE, check_dtype=False) 17 | 18 | 19 | def test_labeled_sensitivity_df(): 20 | df = sensitivity_df( 21 | SENSITIVITY_VALUES_TWO_VALUE, 22 | add_5_to_values, 23 | result_name=RESULT_NAME, 24 | labels=TWO_VALUE_LABELS 25 | ) 26 | 27 | assert_frame_equal(df, EXPECT_DF_TWO_VALUE_LABELS, check_dtype=False) -------------------------------------------------------------------------------- /docsrc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=dero 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /get_logo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | 4 | import requests 5 | 6 | import conf 7 | 8 | DOCS_OUT_FOLDER = pathlib.Path('docsrc') / 'source' / '_static' / 'images' 9 | DOCS_OUT_PATH = DOCS_OUT_FOLDER / 'logo.svg' 10 | 11 | 12 | def download_logo(logo_url = conf.PACKAGE_LOGO_URL, out_path: str = str(DOCS_OUT_PATH)): 13 | if not logo_url: 14 | return 15 | 16 | print(f'Downloading logo from {logo_url}') 17 | response = requests.get(logo_url) 18 | if response.status_code != 200: 19 | raise NoLogoAtUrlException(logo_url) 20 | 21 | content = response.content.decode('utf8') 22 | with open(out_path, 'w') as f: 23 | f.write(content) 24 | print(f'Logo outputted to {out_path}') 25 | 26 | 27 | class NoLogoAtUrlException(Exception): 28 | pass 29 | 30 | 31 | if __name__ == '__main__': 32 | if not os.path.exists(DOCS_OUT_FOLDER): 33 | os.makedirs(DOCS_OUT_FOLDER) 34 | 35 | download_logo() 36 | -------------------------------------------------------------------------------- /scripts/workflows-updated.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$(git diff --stat HEAD -- .github/workflows/)" ]; then 4 | echo "Updates to workflows detected."; 5 | echo ::set-output name=workflow_updated::true; 6 | cat << EOF > temp-issue-template.md; 7 | --- 8 | title: Manual Update to Files from Cookiecutter Needed 9 | labels: automated issue, maintenance 10 | --- 11 | The template from the [Cookiecutter which created this project][1] must be updated using Cruft. 12 | 13 | Normally this is an automated process, but the current updates include changes to the 14 | Github Actions workflow files, and Github Actions does not allow those to be updated 15 | by another workflow. 16 | 17 | Run \`pipenv run cruft update -s\` then manually review and update the changes, before pushing a PR 18 | for this. 19 | 20 | [1]: https://github.com/nickderobertis/cookiecutter-pypi-sphinx 21 | 22 | EOF 23 | else 24 | echo "No updates to workflows."; 25 | echo ::set-output name=workflow_updated::false; 26 | fi; -------------------------------------------------------------------------------- /upload.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence 2 | from subprocess import run 3 | from distutils.core import run_setup 4 | 5 | import conf 6 | import version 7 | 8 | 9 | DISTRIBUTION_NAME = f'{conf.PACKAGE_NAME}-{version.__version__}' 10 | DISTRIBUTION_PATH = f'dist/{DISTRIBUTION_NAME}.tar.gz' 11 | 12 | 13 | def twine(main_command: str): 14 | command = f'twine {main_command} {DISTRIBUTION_PATH}' 15 | run(command, shell=True, check=True) 16 | 17 | 18 | def upload_app(build_only: bool = False): 19 | run_setup('setup.py', script_args=['sdist', 'bdist_wheel']) 20 | if build_only: 21 | return 22 | twine('upload') 23 | 24 | if __name__ == '__main__': 25 | import argparse 26 | parser = argparse.ArgumentParser(description=f'Build and upload {conf.PACKAGE_NAME} to PyPi') 27 | parser.add_argument( 28 | '--build-only', 29 | action='store_true', 30 | help='Only build the package, do not upload' 31 | ) 32 | 33 | args = parser.parse_args() 34 | 35 | upload_app(build_only=args.build_only) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # Editor files 61 | .idea 62 | 63 | .mypy_cache 64 | docs 65 | docsrc/source/auto_examples 66 | _examples 67 | **/.ipynb_checkpoints 68 | .vscode 69 | Scratch -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 Nick DeRobertis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /docsrc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = sensitivity 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | autodoc: 18 | @sphinx-apidoc -M -o ./source/api -t ./apidoc/templates "../$(SPHINXPROJ)" 19 | 20 | cleandoc: 21 | @rm -rf ./source/api 22 | @rm -rf ./source/stubs 23 | @rm -rf ./build 24 | @rm -rf ./source/auto_examples 25 | @rm -rf ../_examples 26 | @rm -rf ./source/binder/requirements.txt 27 | 28 | github: 29 | @make cleandoc 30 | @./binder_requirements.sh 31 | @./nb-examples.sh 32 | @./download-logo.sh 33 | @make doctest 34 | @make autodoc 35 | @make html 36 | @cp -a build/html/. ../docs 37 | 38 | # Catch-all target: route all unknown targets to Sphinx using the new 39 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 40 | %: Makefile 41 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 42 | 43 | -------------------------------------------------------------------------------- /docsrc/apidoc/templates/package.rst_t: -------------------------------------------------------------------------------- 1 | 2 | {%- macro automodule(modname, options) -%} 3 | .. automodule:: {{ modname }} 4 | {%- for option in options %} 5 | :{{ option }}: 6 | {%- endfor %} 7 | {%- endmacro %} 8 | 9 | {%- macro toctree(docnames) -%} 10 | .. toctree:: 11 | {% for docname in docnames %} 12 | {{ docname }} 13 | {%- endfor %} 14 | {%- endmacro %} 15 | 16 | {%- if is_namespace %} 17 | {{- [pkgname, "namespace"] | join(" ") | e | heading }} 18 | {% else %} 19 | {{- [pkgname, "package"] | join(" ") | e | heading }} 20 | {% endif %} 21 | 22 | {%- if modulefirst and not is_namespace %} 23 | {{ automodule(pkgname, automodule_options) }} 24 | {% endif %} 25 | 26 | {%- if subpackages %} 27 | Subpackages 28 | ----------- 29 | 30 | {{ toctree(subpackages) }} 31 | {% endif %} 32 | 33 | {%- if submodules %} 34 | Submodules 35 | ---------- 36 | {% if separatemodules %} 37 | {{ toctree(submodules) }} 38 | {%- else %} 39 | {%- for submodule in submodules %} 40 | {% if show_headings %} 41 | {{- [submodule, "module"] | join(" ") | e | heading(2) }} 42 | {% endif %} 43 | {{ automodule(submodule, automodule_options) }} 44 | {% endfor %} 45 | {%- endif %} 46 | {% endif %} 47 | 48 | {%- if not modulefirst and not is_namespace %} 49 | Module contents 50 | --------------- 51 | 52 | {{ automodule(pkgname, automodule_options) }} 53 | {% endif %} 54 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import conf 2 | from version import __version__ 3 | from setuptools import setup, find_packages 4 | 5 | extra_kwargs = {} 6 | 7 | entry_points = None 8 | if conf.CONSOLE_SCRIPTS: 9 | entry_points = dict(console_scripts=conf.CONSOLE_SCRIPTS) 10 | 11 | extras_require = None 12 | if conf.OPTIONAL_PACKAGE_INSTALL_REQUIRES: 13 | extras_require = conf.OPTIONAL_PACKAGE_INSTALL_REQUIRES 14 | 15 | long_description = conf.PACKAGE_DESCRIPTION 16 | if conf.PACKAGE_DESCRIPTION.strip().lower() == 'auto': 17 | with open('README.md', 'r') as f: 18 | long_description = f.read() 19 | extra_kwargs['long_description_content_type'] = 'text/markdown' 20 | 21 | setup( 22 | name=conf.PACKAGE_NAME, 23 | version=__version__, 24 | description=conf.PACKAGE_SHORT_DESCRIPTION, 25 | long_description=long_description, 26 | author=conf.PACKAGE_AUTHOR, 27 | author_email=conf.PACKAGE_AUTHOR_EMAIL, 28 | license=conf.PACKAGE_LICENSE, 29 | packages=find_packages(), 30 | include_package_data=True, 31 | classifiers=conf.PACKAGE_CLASSIFIERS, 32 | install_requires=conf.PACKAGE_INSTALL_REQUIRES, 33 | extras_require=extras_require, 34 | project_urls=conf.PACKAGE_URLS, 35 | url=conf.PACKAGE_URLS['Code'], 36 | scripts=conf.SCRIPTS, 37 | entry_points=entry_points, 38 | **extra_kwargs 39 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![](https://codecov.io/gh/nickderobertis/sensitivity/branch/master/graph/badge.svg)](https://codecov.io/gh/nickderobertis/sensitivity) 3 | 4 | # sensitivity 5 | 6 | ## Overview 7 | 8 | Python Sensitivity Analysis - Gradient DataFrames and Hex-Bin Plots 9 | 10 | It is common in financial modeling to conduct a sensitivity analysis on the model. This analysis runs the model changing the inputs values and collecting the outputs. Then the modeler can examine how the outputs change in response to the inputs changing. This library was created to ease this process, especially around visualization of the results. 11 | 12 | While it was developed for financial modeling, it can be used with any function to understand how changing the inputs of the function affect the outputs. 13 | 14 | ## Getting Started 15 | 16 | Install `sensitivity`: 17 | 18 | ``` 19 | pip install sensitivity 20 | ``` 21 | 22 | A simple example: 23 | 24 | ```python 25 | from sensitivity import SensitivityAnalyzer 26 | 27 | def my_model(x_1, x_2): 28 | return x_1 ** x_2 29 | 30 | sensitivity_dict = { 31 | 'x_1': [10, 20, 30], 32 | 'x_2': [1, 2, 3] 33 | } 34 | 35 | sa = SensitivityAnalyzer(sensitivity_dict, my_model) 36 | plot = sa.plot() 37 | styled_df = sa.styled_dfs() 38 | ``` 39 | 40 | ## Links 41 | 42 | See the 43 | [documentation here.]( 44 | https://nickderobertis.github.io/sensitivity/ 45 | ) 46 | 47 | ## Author 48 | 49 | Created by Nick DeRobertis. MIT License. -------------------------------------------------------------------------------- /docsrc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. sensitivity documentation master file, created by 2 | cookiecutter-pypi-sphinx. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Python Sensitivity Analysis documentation! 7 | ******************************************************************** 8 | 9 | Python Sensitivity Analysis - Gradient DataFrames and Hex-Bin Plots 10 | 11 | To get started, look here. 12 | 13 | .. toctree:: 14 | :caption: Tutorial 15 | 16 | overview 17 | tutorial 18 | auto_examples/index 19 | 20 | An overview 21 | =========== 22 | 23 | Quick Links 24 | ------------ 25 | 26 | Find the source code `on Github `_. 27 | 28 | 29 | sensitivity 30 | ------------------------------------------------------- 31 | 32 | 33 | This is a simple example: 34 | 35 | .. code:: python 36 | 37 | from sensitivity import SensitivityAnalyzer 38 | 39 | def my_model(x_1, x_2): 40 | return x_1 ** x_2 41 | 42 | sensitivity_dict = { 43 | 'x_1': [10, 20, 30], 44 | 'x_2': [1, 2, 3] 45 | } 46 | 47 | sa = SensitivityAnalyzer(sensitivity_dict, my_model) 48 | plot = sa.plot() 49 | styled_df = sa.styled_dfs() 50 | 51 | 52 | .. toctree:: api/modules 53 | :caption: API Documentation 54 | :maxdepth: 3 55 | 56 | Indices and tables 57 | ================== 58 | 59 | * :ref:`genindex` 60 | * :ref:`modindex` 61 | * :ref:`search` 62 | -------------------------------------------------------------------------------- /tests/input_data/df_styled_num_fmt.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | 20 | 21 | 23 | 26 | 29 | 30 | 31 | 34 | 36 | 38 | 39 | 40 | 41 | 42 | 45 | 48 | 51 | 52 | 53 | 56 | 59 | 62 | 63 | 64 |
17 | my_res - value1 vs. value2 18 |
22 | 24 | 4 25 | 27 | 5 28 |
32 | value1 33 | 35 | 37 |
43 | 1 44 | 46 | $10 47 | 49 | $11 50 |
54 | 2 55 | 57 | $11 58 | 60 | $12 61 |
65 | -------------------------------------------------------------------------------- /tests/input_data/df_styled.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | 20 | 21 | 23 | 26 | 29 | 30 | 31 | 34 | 36 | 38 | 39 | 40 | 41 | 42 | 45 | 48 | 51 | 52 | 53 | 56 | 59 | 62 | 63 | 64 |
17 | my_res - value1 vs. value2 18 |
22 | 24 | 4 25 | 27 | 5 28 |
32 | value1 33 | 35 | 37 |
43 | 1 44 | 46 | 10.000000 47 | 49 | 11.000000 50 |
54 | 2 55 | 57 | 11.000000 58 | 60 | 12.000000 61 |
65 | -------------------------------------------------------------------------------- /tests/input_data/df_labeled.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 19 | 20 | 21 | 23 | 26 | 29 | 30 | 31 | 34 | 36 | 38 | 39 | 40 | 41 | 42 | 45 | 48 | 51 | 52 | 53 | 56 | 59 | 62 | 63 | 64 |
17 | my_res - Formatted 1 vs. Formatted 2 18 |
22 | 24 | 4 25 | 27 | 5 28 |
32 | Formatted 1 33 | 35 | 37 |
43 | 1 44 | 46 | 10.000000 47 | 49 | 11.000000 50 |
54 | 2 55 | 57 | 11.000000 58 | 60 | 12.000000 61 |
65 | -------------------------------------------------------------------------------- /docsrc/source/overview.rst: -------------------------------------------------------------------------------- 1 | Overview of the sensitivity package 2 | ************************************** 3 | 4 | Purpose 5 | ======= 6 | 7 | It is common in financial modeling to conduct a sensitivity 8 | analysis on the model. This analysis runs the model changing 9 | the inputs values and collecting the outputs. Then the modeler 10 | can examine how the outputs change in response to the inputs 11 | changing. This library was created to ease this process, 12 | especially around visualization of the results. 13 | 14 | While it was developed for financial modeling, it can be used with 15 | any function to understand how changing the inputs of the function 16 | affect the outputs. 17 | 18 | What Does it Do? 19 | ================== 20 | 21 | The main logic of the ``SensitivityAnalyzer`` is replicating 22 | a nested loop over the input values. Let's look at the basic 23 | example of how to use ``SensitivityAnalyzer``:: 24 | 25 | from sensitivity import SensitivityAnalyzer 26 | 27 | def my_model(x_1, x_2): 28 | return x_1 ** x_2 29 | 30 | sensitivity_dict = { 31 | 'x_1': [10, 20, 30], 32 | 'x_2': [1, 2, 3] 33 | } 34 | 35 | sa = SensitivityAnalyzer(sensitivity_dict, my_model) 36 | sa.df 37 | 38 | This is roughly equivalent to:: 39 | 40 | import pandas as pd 41 | 42 | def my_model(x_1, x_2): 43 | return x_1 ** x_2 44 | 45 | results = [] 46 | for x_1 in [10, 20, 30]: 47 | for x_2 in [1, 2, 3]: 48 | res = my_model(x_1, x_2) 49 | results.append((x_1, x_2, res)) 50 | df = pd.DataFrame(results, columns=['x_1', 'x_2', 'Result']) 51 | 52 | The greater convenience comes with the built-in visualization:: 53 | 54 | plot = sa.plot() 55 | styled_df = sa.styled_dfs() 56 | 57 | Which handles generating hexbin plots and DataFrames with 58 | a background gradient to signify high or low values. 59 | 60 | What Happens with More Inputs? 61 | ================================ 62 | 63 | The hexbin plots and styled DataFrames can only display two 64 | inputs changing at once, but it is possible to run 65 | ``SensitivityAnalyzer`` with as many inputs as you want. 66 | 67 | To work with more than two inputs, ``SensitivityAnalyzer`` 68 | has two approaches: first, it displays as many hexbin plots 69 | or styled DataFrames as needed to have the pairwise combinations 70 | of the inputs. E.g. with inputs 1, 2, and 3, there would be three plots, 71 | one for 1 and 2, one for 2 and 3, and one for 1 and 3. But even this 72 | is not enough, because in that example, for each combination of 1 and 2, 73 | there will be multiple results, one for each value of input 3. So 74 | it is also necessary to aggregate the results to reach a single result 75 | for the combination of the two inputs. ``SensitivityAnalyzer`` 76 | by default will take the mean, but it exposes the ``agg_func`` 77 | argument which should accept a list of values and return a single 78 | value, so the user can pick any aggregation such as ``numpy``'s 79 | ``median`` or ``std`` function. -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build and Push Docs 3 | 4 | on: 5 | push: 6 | paths: 7 | - 'docsrc/**' 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | # TODO [#2]: refactor docs workflow once Github Actions improves 14 | # 15 | # Entire jobs are getting copied between workflow files due to limitations in Github Actions 16 | # 17 | # Possible changes to Github Actions that would allow the docs workflow to be refactored: 18 | # - reuse jobs 19 | # - reuse steps 20 | # - trigger workflow from within action/workflow 21 | test: 22 | 23 | runs-on: ubuntu-latest 24 | strategy: 25 | max-parallel: 1 26 | matrix: 27 | python-version: [3.8] 28 | 29 | steps: 30 | - uses: actions/checkout@v1 31 | - name: Set up Python ${{ matrix.python-version }} 32 | uses: actions/setup-python@v1 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | - name: Install Pipenv 36 | uses: dschep/install-pipenv-action@v1 37 | - name: Install dependencies 38 | run: | 39 | pipenv sync 40 | - name: Lint with flake8 41 | run: | 42 | pip install flake8 43 | # stop the build if there are Python syntax errors or undefined names 44 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 45 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 46 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 47 | - name: Test with pytest 48 | run: | 49 | pipenv run python -m pytest --cov=./ --cov-report=xml 50 | - name: Upload coverage to Codecov 51 | if: github.ref == 'refs/heads/master' 52 | uses: codecov/codecov-action@v1 53 | with: 54 | token: ${{ secrets.CODECOV_TOKEN }} 55 | file: ./coverage.xml 56 | flags: unittests 57 | name: codecov-pytest 58 | yml: ./codecov.yml 59 | - name: Static Typing Checks with mypy 60 | run: | 61 | pipenv run mypy $(python directory.py) 62 | 63 | deploy: 64 | needs: test 65 | 66 | runs-on: ubuntu-latest 67 | strategy: 68 | max-parallel: 1 69 | matrix: 70 | python-version: [3.8] 71 | 72 | steps: 73 | - uses: actions/checkout@v1 74 | - name: Set up Python ${{ matrix.python-version }} 75 | uses: actions/setup-python@v1 76 | with: 77 | python-version: ${{ matrix.python-version }} 78 | - name: Install Pipenv 79 | uses: dschep/install-pipenv-action@v1 80 | - name: Install dependencies 81 | run: | 82 | pipenv sync 83 | sudo apt-get install pandoc -y 84 | - name: Build Documentation 85 | run: | 86 | cd docsrc 87 | pipenv run make github 88 | cd .. 89 | echo "" > docs/.nojekyll 90 | - name: Deploy Documentation 91 | uses: peaceiris/actions-gh-pages@v2.5.0 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.gh_token }} 94 | PUBLISH_BRANCH: gh-pages 95 | PUBLISH_DIR: ./docs 96 | -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | from copy import deepcopy 3 | from io import BytesIO 4 | from pathlib import Path 5 | 6 | import pandas as pd 7 | from bs4 import BeautifulSoup 8 | from pandas.io.formats.style import Styler 9 | import matplotlib.pyplot as plt 10 | 11 | INPUT_FILES_FOLDER = Path(__file__).parent / "input_data" 12 | DF_STYLED_PATH = os.path.join(INPUT_FILES_FOLDER, 'df_styled.html') 13 | DF_LABELED_PATH = os.path.join(INPUT_FILES_FOLDER, 'df_labeled.html') 14 | DF_STYLED_NUM_FMT_PATH = os.path.join(INPUT_FILES_FOLDER, 'df_styled_num_fmt.html') 15 | DF_STYLE_UUID = '1ee5ad65-4cac-42e3-8133-7ae800cb23ad' 16 | DEFAULT_PLOT_PATH = INPUT_FILES_FOLDER / 'default_plot.png' 17 | PLOT_THREE_PATH = INPUT_FILES_FOLDER / 'plot_three.png' 18 | PLOT_OPTIONS_PATH = INPUT_FILES_FOLDER / 'plot_options.png' 19 | RESULT_NAME = 'my_res' 20 | TWO_VALUE_LABELS = { 21 | 'value1': 'Formatted 1', 22 | 'value2': 'Formatted 2' 23 | } 24 | THREE_VALUE_LABELS = deepcopy(TWO_VALUE_LABELS) 25 | THREE_VALUE_LABELS['value3'] = 'Formatted 3' 26 | EXPECT_DF_TWO_VALUE = pd.DataFrame( 27 | [ 28 | (1, 4, 10), 29 | (1, 5, 11), 30 | (2, 4, 11), 31 | (2, 5, 12), 32 | ], 33 | columns=['value1', 'value2', RESULT_NAME] 34 | ) 35 | EXPECT_DF_TWO_VALUE_LABELS = EXPECT_DF_TWO_VALUE.rename(columns=TWO_VALUE_LABELS) 36 | 37 | EXPECT_DF_THREE_VALUE = pd.DataFrame( 38 | [ 39 | (1, 4, 6, 21), 40 | (1, 4, 7, 22), 41 | (1, 5, 6, 22), 42 | (1, 5, 7, 23), 43 | (2, 4, 6, 22), 44 | (2, 4, 7, 23), 45 | (2, 5, 6, 23), 46 | (2, 5, 7, 24), 47 | ], 48 | columns=['value1', 'value2', 'value3', RESULT_NAME] 49 | ) 50 | 51 | SENSITIVITY_VALUES_TWO_VALUE = { 52 | 'value1': [1, 2], 53 | 'value2': [4, 5], 54 | } 55 | SENSITIVITY_VALUES_THREE_VALUE = deepcopy(SENSITIVITY_VALUES_TWO_VALUE) 56 | SENSITIVITY_VALUES_THREE_VALUE['value3'] = [6, 7] 57 | 58 | 59 | 60 | def add_5_to_values(value1, value2): 61 | return value1 + value2 + 5 62 | 63 | 64 | def add_10_to_values(value1, value2, value3=5): 65 | return value1 + value2 + value3 + 10 66 | 67 | 68 | def assert_styled_matches(styler: Styler, file_path: str = DF_STYLED_PATH, generate: bool = False): 69 | compare_html = styler.set_uuid(DF_STYLE_UUID).to_html() 70 | 71 | if generate: 72 | Path(file_path).write_text(_prettify_html(compare_html)) 73 | 74 | with open(file_path, 'r') as f: 75 | expect_html = f.read() 76 | 77 | assert _prettify_html(compare_html) == _prettify_html(expect_html) 78 | 79 | 80 | def _prettify_html(html: str) -> str: 81 | soup = BeautifulSoup(html, "html.parser") 82 | return soup.prettify() 83 | 84 | 85 | def assert_graph_matches(fig: plt.Figure, file_path: Path = DEFAULT_PLOT_PATH, generate: bool = False): 86 | if generate: 87 | fig.savefig(str(file_path)) 88 | 89 | compare_bytestream = BytesIO() 90 | fig.savefig(compare_bytestream) 91 | compare_bytestream.seek(0) 92 | compare_bytes = compare_bytestream.read() 93 | 94 | expect_bytes = file_path.read_bytes() 95 | 96 | assert compare_bytes == expect_bytes 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /tests/test_sensitivity_analyzer.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from pandas.testing import assert_frame_equal 4 | 5 | from sensitivity import SensitivityAnalyzer 6 | from tests.base import EXPECT_DF_TWO_VALUE, SENSITIVITY_VALUES_TWO_VALUE, add_5_to_values, RESULT_NAME, \ 7 | SENSITIVITY_VALUES_THREE_VALUE, add_10_to_values, EXPECT_DF_THREE_VALUE, assert_styled_matches, \ 8 | DF_STYLED_NUM_FMT_PATH, assert_graph_matches, PLOT_THREE_PATH, PLOT_OPTIONS_PATH, TWO_VALUE_LABELS, DF_LABELED_PATH 9 | 10 | 11 | class TestSensitivityAnalyzer: 12 | 13 | def create_sa(self, **kwargs) -> SensitivityAnalyzer: 14 | sa_config = dict( 15 | sensitivity_values=SENSITIVITY_VALUES_TWO_VALUE, 16 | func=add_5_to_values, 17 | result_name=RESULT_NAME 18 | ) 19 | sa_config.update(**kwargs) 20 | sa = SensitivityAnalyzer(**sa_config) 21 | return sa 22 | 23 | def test_create(self): 24 | sa = self.create_sa() 25 | 26 | def test_create_df(self): 27 | sa = self.create_sa() 28 | assert_frame_equal(sa.df, EXPECT_DF_TWO_VALUE, check_dtype=False) 29 | 30 | def test_create_df_three_values(self): 31 | sa = self.create_sa( 32 | sensitivity_values=SENSITIVITY_VALUES_THREE_VALUE, 33 | func=add_10_to_values, 34 | ) 35 | assert_frame_equal(sa.df, EXPECT_DF_THREE_VALUE, check_dtype=False) 36 | 37 | def test_create_styled_dfs(self): 38 | sa = self.create_sa() 39 | result = sa.styled_dfs() 40 | assert_styled_matches(result) 41 | 42 | def test_create_styled_dfs_with_num_fmt(self): 43 | sa = self.create_sa(num_fmt='${:,.0f}') 44 | result = sa.styled_dfs() 45 | sa2 = self.create_sa() 46 | result2 = sa2.styled_dfs(num_fmt='${:,.0f}') 47 | assert_styled_matches(result, DF_STYLED_NUM_FMT_PATH) 48 | assert_styled_matches(result2, DF_STYLED_NUM_FMT_PATH) 49 | 50 | def test_create_styled_dfs_with_labels(self): 51 | sa = self.create_sa(labels=TWO_VALUE_LABELS) 52 | result = sa.styled_dfs() 53 | assert_styled_matches(result, DF_LABELED_PATH) 54 | 55 | def test_create_styled_dfs_three_values(self): 56 | sa = self.create_sa( 57 | sensitivity_values=SENSITIVITY_VALUES_THREE_VALUE, 58 | func=add_10_to_values, 59 | ) 60 | result = sa.styled_dfs() 61 | 62 | def test_create_plot(self): 63 | sa = self.create_sa() 64 | result = sa.plot() 65 | assert_graph_matches(result) 66 | 67 | def test_create_plot_three_values(self): 68 | sa = self.create_sa( 69 | sensitivity_values=SENSITIVITY_VALUES_THREE_VALUE, 70 | func=add_10_to_values, 71 | ) 72 | result = sa.plot() 73 | assert_graph_matches(result, file_path=PLOT_THREE_PATH) 74 | 75 | def test_create_plot_with_options(self): 76 | options = dict( 77 | grid_size=2, color_map='viridis', reverse_colors=True 78 | ) 79 | sa = self.create_sa(labels=TWO_VALUE_LABELS, **options) 80 | result = sa.plot() 81 | assert_graph_matches(result, file_path=PLOT_OPTIONS_PATH) 82 | sa = self.create_sa(labels=TWO_VALUE_LABELS) 83 | result = sa.plot(**options) 84 | assert_graph_matches(result, file_path=PLOT_OPTIONS_PATH) 85 | -------------------------------------------------------------------------------- /.github/workflows/template-update.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Update Template using Cruft 3 | 4 | on: 5 | schedule: 6 | - cron: '0 3 * * *' # every day at 3:00 AM 7 | 8 | 9 | jobs: 10 | templateUpdate: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | max-parallel: 1 15 | matrix: 16 | python-version: [3.8] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | ref: master 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v1 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install Pipenv 27 | uses: dschep/install-pipenv-action@v1 28 | - name: Install dependencies 29 | run: | 30 | pipenv sync 31 | - name: Check if template is out of date 32 | id: template_check 33 | run: | 34 | if ! pipenv run cruft check; then 35 | echo "Need to update template. Will do so and open PR."; 36 | echo ::set-output name=template_update::true; 37 | else 38 | echo "No updates to template needed, will exit workflow."; 39 | echo ::set-output name=template_update::false; 40 | fi; 41 | - name: Template Update 42 | if: steps.template_check.outputs.template_update == 'true' 43 | run: pipenv run cruft update -s; 44 | - name: Determine if Github Actions Workflow Updated 45 | id: workflow_update_check 46 | if: steps.template_check.outputs.template_update == 'true' 47 | run: bash scripts/workflows-updated.sh 48 | - name: Check if Issue Exists 49 | uses: nickderobertis/check-if-issue-exists-action@master 50 | id: check_if_issue_exists 51 | with: 52 | repo: nickderobertis/sensitivity 53 | token: ${{ secrets.gh_token }} 54 | title: Manual Update to Files from Cookiecutter Needed 55 | labels: automated issue, maintenance 56 | - name: Create Issue to Manually Update Template 57 | if: steps.template_check.outputs.template_update == 'true' && steps.workflow_update_check.outputs.workflow_updated && !steps.check_if_issue_exists.outputs.exists 58 | uses: JasonEtco/create-an-issue@5ee5d5edcd3777b7d5482d9342d3f08cd8daa3cd 59 | with: 60 | filename: temp-issue-template.md 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.gh_token }} 63 | - name: Create PR with template update 64 | if: steps.template_check.outputs.template_update == 'true' && !steps.workflow_update_check.outputs.workflow_updated 65 | uses: peter-evans/create-pull-request@v2 66 | with: 67 | token: ${{ secrets.gh_token }} 68 | commit-message: Update template from Cookiecutter using Cruft 69 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 70 | committer: GitHub 71 | title: 'Update template from Cookiecutter using Cruft' 72 | body: | 73 | Update template from Cookiecutter using Cruft. 74 | 75 | Before merging, review the changes and adjust manually if necessary. Especially look for 76 | new files ending with `.orig` being added as this signifies a merge conflict that 77 | needs to be resolved. 78 | 79 | - Updates coming from [cookiecutter-pypi-sphinx][1] 80 | 81 | [1]: https://github.com/nickderobertis/cookiecutter-pypi-sphinx 82 | labels: no auto merge, automated pr 83 | branch: template-patches 84 | -------------------------------------------------------------------------------- /nbexamples/ipynb_to_gallery.py: -------------------------------------------------------------------------------- 1 | """Convert jupyter notebook to sphinx gallery notebook styled examples. 2 | Usage: python ipynb_to_gallery.py 3 | Dependencies: 4 | pypandoc: install using `pip install pypandoc` 5 | """ 6 | import os 7 | from typing import Optional 8 | 9 | import pypandoc as pdoc 10 | import json 11 | 12 | 13 | def convert_ipynb_to_gallery(file_path: str, out_path: Optional[str] = None): 14 | if out_path is None: 15 | out_path = file_path.replace('.ipynb', '.py') 16 | 17 | python_file = "" 18 | 19 | nb_dict = json.load(open(file_path)) 20 | cells = nb_dict['cells'] 21 | 22 | for i, cell in enumerate(cells): 23 | if i == 0: 24 | assert cell['cell_type'] == 'markdown', \ 25 | 'First cell has to be markdown' 26 | 27 | md_source = ''.join(cell['source']) 28 | rst_source = pdoc.convert_text(md_source, 'rst', 'md') 29 | python_file = '"""\n' + rst_source + '\n"""' 30 | else: 31 | if cell['cell_type'] == 'markdown': 32 | md_source = ''.join(cell['source']) 33 | rst_source = pdoc.convert_text(md_source, 'rst', 'md') 34 | commented_source = '\n'.join(['# ' + x for x in 35 | rst_source.split('\n')]) 36 | python_file = python_file + '\n\n\n' + '#' * 70 + '\n' + \ 37 | commented_source 38 | elif cell['cell_type'] == 'code': 39 | source = ''.join(cell['source']) 40 | python_file = python_file + '\n' * 2 + source 41 | 42 | python_file = python_file.replace("\n%", "\n# %") 43 | with open(out_path, 'w') as f: 44 | f.write(python_file) 45 | 46 | 47 | def convert_all_in_folder_to_gallery(folder: str, out_folder: Optional[str] = None, replace: bool = False): 48 | folder = os.path.normpath(folder) 49 | 50 | if out_folder is None: 51 | out_folder = folder 52 | else: 53 | out_folder = os.path.normpath(out_folder) 54 | 55 | for path, folders, files in os.walk(folder): 56 | if '.ipynb_checkpoints' in path: 57 | # Skip checkpoints folders 58 | continue 59 | sub_path = os.path.sep.join(path.split(os.path.sep)[1:]) # relative path within folder 60 | current_out_folder = os.path.join(out_folder, sub_path) 61 | print(f'Outputting contents of {sub_path} to {current_out_folder}') 62 | if not os.path.exists(current_out_folder): 63 | os.makedirs(current_out_folder) 64 | files = [file for file in files if file.lower().endswith('ipynb')] 65 | for file in files: 66 | file_path = os.path.join(path, file) 67 | out_file = file.lower().replace('.ipynb', '.py').replace(' ', '_') 68 | out_path = os.path.join(current_out_folder, out_file) 69 | if not replace and os.path.exists(out_path): 70 | print(f'Skipping file {file} as .py already exists') 71 | continue 72 | print(f'Converting file {file}') 73 | convert_ipynb_to_gallery(file_path, out_path) 74 | 75 | 76 | if __name__ == '__main__': 77 | import argparse 78 | parser = argparse.ArgumentParser() 79 | parser.add_argument('folder', 80 | help='Folder to convert ipynb to Sphinx Gallery py') 81 | parser.add_argument('-o', '--out-folder', default=None, 82 | help='Output folder for Sphinx Gallery py files, default in same folder') 83 | parser.add_argument('-r', '--replace', action='store_true', 84 | help='Overwrite existing Sphinx Gallery py files') 85 | args = parser.parse_args() 86 | convert_all_in_folder_to_gallery(args.folder, args.out_folder, args.replace) 87 | -------------------------------------------------------------------------------- /sensitivity/hexbin.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Callable, Sequence 2 | import itertools 3 | import math 4 | import pandas as pd 5 | import matplotlib.pyplot as plt 6 | from matplotlib.gridspec import GridSpec 7 | import numpy as np 8 | 9 | from sensitivity.colors import _get_color_map 10 | from sensitivity.df import sensitivity_df 11 | 12 | 13 | def sensitivity_hex_plots(sensitivity_values: Dict[str, Any], func: Callable, 14 | result_name: str = 'Result', agg_func: Callable = np.mean, 15 | reverse_colors: bool = False, grid_size: int = 8, 16 | color_map: str = 'RdYlGn', 17 | **func_kwargs) -> plt.Figure: 18 | """ 19 | Create hexbin plots showing how the func result varies with a passed dictionary of input values. 20 | Automatically creates a plot for each pair of input parameters passed. 21 | 22 | :param sensitivity_values: Dictionary where keys are func's argument names and values are lists of possible 23 | values to use for that argument. 24 | :param func: Function that accepts arguments with names matching the keys of sensitivity_values, and outputs a 25 | scalar value. 26 | :param result_name: Name for result shown in graph color bar label 27 | :param agg_func: If there are multiple results within the hex parameter area, function to aggregate those results to 28 | get a single value for the hex area. The function should accept a sequence of values and return a scalar. 29 | :param reverse_colors: Default is for red to represent low values of result, green for high values. Set 30 | reverse_colors=True to have green represent low values of result and red for high values. 31 | :param grid_size: Number of hex bins on each axis. E.g. passing 5 would create a 5x5 grid, 25 hex bins. 32 | :param color_map: matplotlib color map, default is RdYlGn (red, yellow, green). See 33 | https://matplotlib.org/3.3.2/tutorials/colors/colormaps.html 34 | :param func_kwargs: Additional arguments to pass to func, regardless of the sensitivity values picked 35 | :return: Sensitivity analysis hex bin sub plot figure 36 | """ 37 | s_df = sensitivity_df( 38 | sensitivity_values, 39 | func, 40 | result_name=result_name, 41 | **func_kwargs 42 | ) 43 | sensitivity_cols = list(sensitivity_values.keys()) 44 | return _hex_figure_from_sensitivity_df( 45 | s_df, 46 | sensitivity_cols, 47 | result_name=result_name, 48 | agg_func=agg_func, 49 | reverse_colors=reverse_colors, 50 | grid_size=grid_size, 51 | color_map=color_map, 52 | ) 53 | 54 | 55 | def _hex_figure_from_sensitivity_df(df: pd.DataFrame, sensitivity_cols: Sequence[str], 56 | result_name: str = 'Result', agg_func: Callable = np.mean, 57 | reverse_colors: bool = False, grid_size: int = 8, 58 | color_map: str = 'RdYlGn') -> plt.Figure: 59 | color_str = _get_color_map(reverse_colors=reverse_colors, color_map=color_map) 60 | combos = list(itertools.combinations(sensitivity_cols, 2)) 61 | num_columns = 3 62 | num_rows = int(math.ceil(len(combos) / num_columns)) 63 | gs = GridSpec(num_rows, num_columns) 64 | fig = plt.figure(figsize=(15, 4 * num_rows)) 65 | for i, (x, y) in enumerate(combos): 66 | ax = fig.add_subplot(gs[i]) 67 | hb = ax.hexbin(x=df[x], 68 | y=df[y], 69 | C=df[result_name], 70 | reduce_C_function=agg_func, 71 | gridsize=grid_size, 72 | cmap=color_str) 73 | plt.xlabel(x) 74 | plt.ylabel(y) 75 | cb = fig.colorbar(hb, ax=ax) 76 | cb.set_label(result_name) 77 | fig.tight_layout() 78 | return fig 79 | -------------------------------------------------------------------------------- /sensitivity/df.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from functools import reduce 3 | from typing import Dict, Any, Callable, Sequence, Optional 4 | import itertools 5 | from copy import deepcopy 6 | 7 | import pandas as pd 8 | from pandas.io.formats.style import Styler 9 | import numpy as np 10 | from tqdm import tqdm 11 | 12 | from sensitivity.colors import _get_color_map 13 | 14 | 15 | def sensitivity_df(sensitivity_values: Dict[str, Any], func: Callable, 16 | result_name: str = 'Result', 17 | labels: Optional[Dict[str, str]] = None, 18 | **func_kwargs) -> pd.DataFrame: 19 | """ 20 | Creates a DataFrame containing the results of sensitivity analysis. 21 | 22 | Runs func with the cartesian product of the possible values for each argument, passed 23 | in sensitivity_values. 24 | 25 | :param sensitivity_values: Dictionary where keys are func's argument names and values are lists of possible 26 | values to use for that argument. 27 | :param func: Function that accepts arguments with names matching the keys of sensitivity_values, and outputs a 28 | scalar value. 29 | :param result_name: Name for result shown in graph color bar label 30 | :param labels: Optional dictionary where keys are arguments of the function and values are the displayed names 31 | for these arguments in the styled DataFrames and plots 32 | :param func_kwargs: Additional arguments to pass to func, regardless of the sensitivity values picked 33 | :return: a DataFrame containing the results from sensitivity analysis on func 34 | """ 35 | sensitivity_cols = list(sensitivity_values.keys()) 36 | df = pd.DataFrame(columns=sensitivity_cols + [result_name]) 37 | num_cases = reduce(operator.mul, [len(values) for values in sensitivity_values.values()], 1) 38 | for i in tqdm(itertools.product(*sensitivity_values.values()), total=num_cases): 39 | base_param_dict = dict(zip(sensitivity_cols, i)) 40 | param_dict = deepcopy(base_param_dict) 41 | param_dict.update(func_kwargs) 42 | result = func(**param_dict) 43 | base_param_dict.update({result_name: result}) 44 | df = pd.concat([df,pd.DataFrame(pd.Series(base_param_dict)).T]) 45 | df.reset_index(drop=True, inplace=True) 46 | df = df.convert_dtypes() 47 | if labels: 48 | df.rename(columns=labels, inplace=True) 49 | 50 | return df 51 | 52 | 53 | def _two_variable_sensitivity_display_df(df: pd.DataFrame, col1: str, col2: str, 54 | result_col: str = 'Result', agg_func: Callable = np.mean) -> pd.DataFrame: 55 | df_or_series = df[[col1, col2, result_col]].groupby([col1, col2]).apply(agg_func) 56 | if isinstance(df_or_series, pd.DataFrame): 57 | series = df_or_series[result_col] 58 | elif isinstance(df_or_series, pd.Series): 59 | series = df_or_series 60 | else: 61 | raise ValueError(f'expected Series or DataFrame, got {df_or_series} of type {type(df_or_series)}') 62 | selected_df = series.reset_index() 63 | 64 | wide_df = selected_df.pivot(index=col1, columns=col2, values=result_col) 65 | wide_df.columns.name = None 66 | 67 | # Fix for an odd Pandas bug introduced in 1.5 68 | # Even though this is effectively a no-op, without this was getting the following error 69 | # once it would try to do .style.to_html() on the returned DataFrame 70 | # IndexError: Boolean index has wrong length: 1 instead of 2 71 | wide_df = wide_df.reset_index().set_index(col1) 72 | 73 | return wide_df 74 | 75 | 76 | def _style_sensitivity_df(df: pd.DataFrame, col1: str, col2: Optional[str] = None, result_col: str = 'Result', 77 | reverse_colors: bool = False, 78 | col_subset: Optional[Sequence[str]] = None, 79 | num_fmt: Optional[str] = None, color_map: str = 'RdYlGn') -> Styler: 80 | if col2 is not None: 81 | caption = f'{result_col} - {col1} vs. {col2}' 82 | else: 83 | caption = f'{result_col} vs. {col1}' 84 | 85 | if num_fmt is not None: 86 | fmt_dict = {col: num_fmt for col in df.columns} 87 | styler = df.style.format(fmt_dict) 88 | else: 89 | styler = df.style 90 | 91 | color_str = _get_color_map(reverse_colors=reverse_colors, color_map=color_map) 92 | return styler.background_gradient( 93 | cmap=color_str, subset=col_subset, axis=None 94 | ).set_caption(caption) 95 | 96 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | # This is the main settings file for package setup and PyPi deployment. 2 | # Sphinx configuration is in the docsrc folder 3 | 4 | # Main package name 5 | PACKAGE_NAME = "sensitivity" 6 | 7 | # Directory name of package 8 | PACKAGE_DIRECTORY = "sensitivity" 9 | 10 | # Name of Repo 11 | REPO_NAME = "sensitivity" 12 | 13 | # Github username of the user which owns the repo 14 | REPO_USERNAME = "nickderobertis" 15 | 16 | # List of maintainers of package, by default the same user which owns the repo 17 | # Pull requests raised by these maintainers without the "no auto merge" label will be automatically merged 18 | REPO_MAINTAINERS = [ 19 | REPO_USERNAME, 20 | ] 21 | 22 | # Package version in the format (major, minor, release) 23 | PACKAGE_VERSION_TUPLE = (0, 2, 8) 24 | 25 | # Short description of the package 26 | PACKAGE_SHORT_DESCRIPTION = "Python Sensitivity Analysis - Gradient DataFrames and Hex-Bin Plots" 27 | 28 | # Long description of the package for PyPI 29 | # Set to 'auto' to use README.md as the PyPI description 30 | # Any other string will be used directly as the PyPI description 31 | PACKAGE_DESCRIPTION = 'auto' 32 | 33 | # Author 34 | PACKAGE_AUTHOR = "Nick DeRobertis" 35 | 36 | # Author email 37 | PACKAGE_AUTHOR_EMAIL = "whoopnip@gmail.com" 38 | 39 | # Name of license for package 40 | PACKAGE_LICENSE = 'MIT' 41 | 42 | # Classifications for the package, see common settings below 43 | PACKAGE_CLASSIFIERS = [ 44 | # How mature is this project? Common values are 45 | # 3 - Alpha 46 | # 4 - Beta 47 | # 5 - Production/Stable 48 | 'Development Status :: 3 - Alpha', 49 | 50 | # Indicate who your project is intended for 51 | 'Intended Audience :: Developers', 52 | 53 | # Specify the Python versions you support here. In particular, ensure 54 | # that you indicate whether you support Python 2, Python 3 or both. 55 | 'Programming Language :: Python :: 3.6', 56 | 'Programming Language :: Python :: 3.7' 57 | ] 58 | 59 | # Add any third party packages you use in requirements here 60 | PACKAGE_INSTALL_REQUIRES = [ 61 | # Include the names of the packages and any required versions in as strings 62 | # e.g. 63 | # 'package', 64 | # 'otherpackage>=1,<2' 65 | 'pandas>=1', 66 | 'matplotlib', 67 | 'IPython', 68 | 'tqdm', 69 | ] 70 | 71 | # Add any third party packages you use in requirements for optional features of your package here 72 | # Keys should be name of the optional feature and values are lists of required packages 73 | # E.g. {'feature1': ['pandas', 'numpy'], 'feature2': ['matplotlib']} 74 | OPTIONAL_PACKAGE_INSTALL_REQUIRES = { 75 | 76 | } 77 | 78 | # Packages added to Binder environment so that examples can be executed in Binder 79 | # By default, takes this package (PACKAGE_NAME) 80 | # everything the package requires (PACKAGE_INSTALL_REQUIRES) and everything 81 | # that the package optionally requires (OPTIONAL_PACKAGE_INSTALL_REQUIRES) and adds them all to one list 82 | # If a custom list is passed, it must include all the requirements for the Binder environment 83 | BINDER_ENVIRONMENT_REQUIRES = list( 84 | set( 85 | PACKAGE_INSTALL_REQUIRES + [PACKAGE_NAME] + 86 | [package for package_list in OPTIONAL_PACKAGE_INSTALL_REQUIRES.values() for package in package_list] 87 | ) 88 | ) 89 | 90 | 91 | # Sphinx executes all the import statements as it generates the documentation. To avoid having to install all 92 | # the necessary packages, third-party packages can be passed to mock imports to just skip the import. 93 | # By default, everything in PACKAGE_INSTALL_REQUIRES will be passed as mock imports, along with anything here. 94 | # This variable is useful if a package includes multiple packages which need to be ignored. 95 | DOCS_OTHER_MOCK_IMPORTS = [ 96 | # Include the names of the packages as they would be imported, e.g. 97 | # 'package', 98 | ] 99 | 100 | # Add any Python scripts which should be exposed to the command line in the format: 101 | # CONSOLE_SCRIPTS = ['funniest-joke=funniest.command_line:main'] 102 | CONSOLE_SCRIPTS = [], 103 | 104 | # Add any arbitrary scripts to be exposed to the command line in the format: 105 | # SCRIPTS = ['bin/funniest-joke'] 106 | SCRIPTS = [] 107 | 108 | # Optional Google Analytics tracking ID for documentation 109 | # Go to https://analytics.google.com/ and set it up for your documentation URL 110 | # Set to None or empty string to not use this 111 | GOOGLE_ANALYTICS_TRACKING_ID = "UA-158144725-1" 112 | 113 | PACKAGE_URLS = { 114 | 'Code': f'https://github.com/{REPO_USERNAME}/{REPO_NAME}', 115 | 'Documentation': f'https://{REPO_USERNAME}.github.io/{REPO_NAME}' 116 | } 117 | 118 | # Url of logo 119 | PACKAGE_LOGO_URL = "" 120 | 121 | # Does not affect anything about the current package. Simply used for tracking when this repo was created off 122 | # of the quickstart template, so it is easier to bring over new changes to the template. 123 | _TEMPLATE_VERSION_TUPLE = (0, 9, 2) 124 | 125 | if __name__ == '__main__': 126 | # Store config as environment variables 127 | env_vars = dict(locals()) 128 | # Imports after getting locals so that locals are only environment variables 129 | import shlex 130 | for name, value in env_vars.items(): 131 | quoted_value = shlex.quote(str(value)) 132 | print(f'export {name}={quoted_value};') 133 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Test, Build and Push Python Package and Docs 3 | 4 | on: 5 | push: 6 | branches: 7 | - '**' 8 | tags-ignore: 9 | - '**' 10 | pull_request: 11 | types: [closed] 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | max-parallel: 1 18 | matrix: 19 | python-version: [3.8] 20 | 21 | steps: 22 | - name: Dump GitHub context 23 | env: 24 | GITHUB_CONTEXT: ${{ toJson(github) }} 25 | run: echo "$GITHUB_CONTEXT" 26 | - name: Exit Workflow if PR but not Merged 27 | if: github.event.action == 'closed' 28 | run: | 29 | if [ !$GITHUB_PR_BASE == 'master' ]; then 30 | echo "Not merging into master, exiting workflow"; 31 | exit 1; 32 | fi; 33 | if $GITHUB_PR_MERGED; then 34 | echo "PR merged, can continue workflow" 35 | exit 0; 36 | fi; 37 | echo "Got closed PR, not merged, exiting workflow"; 38 | exit 1; 39 | env: 40 | GITHUB_PR_MERGED: ${{ github.event.pull_request.merged }} 41 | GITHUB_PR_BASE: ${{ github.event.pull_request.base.ref }} 42 | - uses: actions/checkout@v1 43 | - name: Set up Python ${{ matrix.python-version }} 44 | uses: actions/setup-python@v1 45 | with: 46 | python-version: ${{ matrix.python-version }} 47 | - name: Install Pipenv 48 | uses: dschep/install-pipenv-action@v1 49 | - name: Install dependencies 50 | run: | 51 | pipenv sync 52 | - name: Lint with flake8 53 | run: | 54 | pip install flake8 55 | # stop the build if there are Python syntax errors or undefined names 56 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 57 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 58 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 59 | - name: Static Typing Checks with mypy 60 | run: | 61 | pipenv run mypy $(python directory.py) 62 | - name: Test with pytest 63 | run: | 64 | pipenv run python -m pytest --cov=./ --cov-report=xml 65 | - name: Upload coverage to Codecov 66 | if: github.ref == 'refs/heads/master' 67 | uses: codecov/codecov-action@v1 68 | with: 69 | token: ${{ secrets.CODECOV_TOKEN }} 70 | file: ./coverage.xml 71 | flags: unittests 72 | name: codecov-pytest 73 | yml: ./codecov.yml 74 | 75 | 76 | collectTODO: 77 | if: github.ref == 'refs/heads/master' 78 | needs: test 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: "actions/checkout@master" 82 | - name: "TODO to Issue" 83 | uses: "alstr/todo-to-issue-action@v4.3" 84 | id: "todo" 85 | 86 | deploy: 87 | if: github.ref == 'refs/heads/master' 88 | needs: test 89 | runs-on: ubuntu-latest 90 | strategy: 91 | max-parallel: 1 92 | matrix: 93 | python-version: [3.8] 94 | steps: 95 | - uses: actions/checkout@v1 96 | - name: Set up Python ${{ matrix.python-version }} 97 | uses: actions/setup-python@v1 98 | with: 99 | python-version: ${{ matrix.python-version }} 100 | - name: Get package name 101 | id: package_name 102 | run: echo ::set-output name=package_name::$(python name.py) 103 | - uses: nickderobertis/pypi-latest-version-action@master 104 | id: output_pypi_version 105 | with: 106 | package: ${{ steps.package_name.outputs.package_name }} 107 | - name: Output build version 108 | id: output_build_version 109 | run: | 110 | bash output-version.sh 111 | - name: Install Pipenv 112 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 113 | uses: dschep/install-pipenv-action@v1 114 | - name: Install dependencies 115 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 116 | run: | 117 | pipenv sync 118 | sudo apt-get install pandoc -y 119 | - name: Build Documentation 120 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 121 | run: | 122 | cd docsrc 123 | pipenv run make github 124 | cd .. 125 | echo "" > docs/.nojekyll 126 | - name: Deploy Documentation 127 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 128 | continue-on-error: true 129 | uses: peaceiris/actions-gh-pages@v2.5.0 130 | env: 131 | GITHUB_TOKEN: ${{ secrets.gh_token }} 132 | PUBLISH_BRANCH: gh-pages 133 | PUBLISH_DIR: ./docs 134 | - name: Build PyPI Package 135 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 136 | run: | 137 | pipenv run python upload.py --build-only 138 | - name: Upload PyPI Package 139 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 140 | uses: pypa/gh-action-pypi-publish@master 141 | with: 142 | user: __token__ 143 | password: ${{ secrets.pypi_password }} 144 | - name: Publish Github Release 145 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 146 | uses: release-drafter/release-drafter@v5 147 | with: 148 | name: "v${{steps.output_build_version.outputs.version}}" 149 | tag: "v${{steps.output_build_version.outputs.version}}" 150 | version: "v${{steps.output_build_version.outputs.version}}" 151 | publish: true 152 | env: 153 | GITHUB_TOKEN: ${{ secrets.gh_token }} 154 | 155 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | 2 | name: automerge 3 | on: 4 | pull_request: 5 | types: 6 | - unlabeled 7 | - synchronize 8 | - opened 9 | - edited 10 | - ready_for_review 11 | - reopened 12 | - unlocked 13 | pull_request_review: 14 | types: 15 | - submitted 16 | jobs: 17 | test: 18 | 19 | runs-on: ubuntu-latest 20 | strategy: 21 | max-parallel: 1 22 | matrix: 23 | python-version: [3.8] 24 | 25 | steps: 26 | - name: Dump GitHub context 27 | env: 28 | GITHUB_CONTEXT: ${{ toJson(github) }} 29 | run: echo "$GITHUB_CONTEXT" 30 | - uses: actions/checkout@v1 31 | - name: Set up Python ${{ matrix.python-version }} 32 | uses: actions/setup-python@v1 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | - name: Install Pipenv 36 | uses: dschep/install-pipenv-action@v1 37 | - name: Install dependencies 38 | run: | 39 | pipenv sync 40 | - name: Lint with flake8 41 | run: | 42 | pip install flake8 43 | # stop the build if there are Python syntax errors or undefined names 44 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 45 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 46 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 47 | - name: Test with pytest 48 | run: | 49 | pipenv run python -m pytest --cov=./ --cov-report=xml 50 | - name: Static Typing Checks with mypy 51 | run: | 52 | pipenv run mypy $(python directory.py) 53 | automerge: 54 | runs-on: ubuntu-latest 55 | needs: test 56 | steps: 57 | - uses: actions/checkout@v1 58 | - name: Set up Python 3.8 59 | uses: actions/setup-python@v1 60 | with: 61 | python-version: 3.8 62 | - name: Check if maintainer 63 | env: 64 | GITHUB_PR_USER: ${{ github.actor }} 65 | run: | 66 | python is_maintainer.py; 67 | exit $?; 68 | - name: Auto-merge PR 69 | id: automerge 70 | if: success() 71 | uses: "nickderobertis/automerge-action@merged-to-use-temporarily" 72 | env: 73 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 74 | MERGE_LABELS: "!no auto merge" 75 | UPDATE_LABELS: "!no auto merge" 76 | - name: Stop if Not Merged 77 | run: | 78 | if $PR_WAS_MERGED; then 79 | echo "PR was merged, now need to deploy" 80 | exit 0; 81 | fi; 82 | echo "PR was not merged, will not auto-deploy" 83 | exit 1; 84 | env: 85 | PR_WAS_MERGED: ${{ steps.automerge.outputs.merged }} 86 | 87 | # TODO [#48]: refactor auto-merge workflow once Github Actions improves 88 | # 89 | # Entire jobs are getting copied between workflow files due to limitations in Github Actions. 90 | # The only difference in these jobs is that they checkout master instead of requiring master 91 | # 92 | # Possible changes to Github Actions that would allow the automerge workflow to be refactored: 93 | # - reuse jobs 94 | # - reuse steps 95 | # - trigger workflow from within action/workflow 96 | # - commit triggered by action triggers push event 97 | collectTODO: 98 | needs: automerge 99 | runs-on: ubuntu-latest 100 | steps: 101 | - uses: "actions/checkout@master" 102 | - name: "TODO to Issue" 103 | uses: "nickderobertis/todo-to-issue-action@pr-support" 104 | id: "todo" 105 | 106 | deploy: 107 | needs: automerge 108 | runs-on: ubuntu-latest 109 | strategy: 110 | max-parallel: 1 111 | matrix: 112 | python-version: [3.8] 113 | steps: 114 | - uses: actions/checkout@v1 115 | with: 116 | ref: refs/heads/master 117 | - name: Set up Python ${{ matrix.python-version }} 118 | uses: actions/setup-python@v1 119 | with: 120 | python-version: ${{ matrix.python-version }} 121 | - name: Get package name 122 | id: package_name 123 | run: echo ::set-output name=package_name::$(python name.py) 124 | - uses: nickderobertis/pypi-latest-version-action@master 125 | id: output_pypi_version 126 | with: 127 | package: ${{ steps.package_name.outputs.package_name }} 128 | - name: Output build version 129 | id: output_build_version 130 | run: | 131 | bash output-version.sh 132 | - name: Install Pipenv 133 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 134 | uses: dschep/install-pipenv-action@v1 135 | - name: Install dependencies 136 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 137 | run: | 138 | pipenv sync 139 | sudo apt-get install pandoc -y 140 | - name: Build Documentation 141 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 142 | run: | 143 | cd docsrc 144 | pipenv run make github 145 | cd .. 146 | echo "" > docs/.nojekyll 147 | - name: Deploy Documentation 148 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 149 | uses: peaceiris/actions-gh-pages@v2.5.0 150 | env: 151 | GITHUB_TOKEN: ${{ secrets.gh_token }} 152 | PUBLISH_BRANCH: gh-pages 153 | PUBLISH_DIR: ./docs 154 | - name: Build PyPI Package 155 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 156 | run: | 157 | pipenv run python upload.py --build-only 158 | - name: Upload PyPI Package 159 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 160 | uses: pypa/gh-action-pypi-publish@master 161 | with: 162 | user: __token__ 163 | password: ${{ secrets.pypi_password }} 164 | - name: Publish Github Release 165 | if: steps.output_pypi_version.outputs.version != steps.output_build_version.outputs.version 166 | uses: release-drafter/release-drafter@v5 167 | with: 168 | name: "v${{steps.output_build_version.outputs.version}}" 169 | tag: "v${{steps.output_build_version.outputs.version}}" 170 | version: "v${{steps.output_build_version.outputs.version}}" 171 | publish: true 172 | env: 173 | GITHUB_TOKEN: ${{ secrets.gh_token }} 174 | -------------------------------------------------------------------------------- /sensitivity/main.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | from dataclasses import dataclass 3 | from typing import Dict, Any, Callable, Optional, List, Union, Sequence 4 | 5 | import numpy as np 6 | import pandas as pd 7 | from pandas.io.formats.style import Styler 8 | import matplotlib.pyplot as plt 9 | from IPython.display import display, HTML 10 | 11 | from sensitivity.df import sensitivity_df, _style_sensitivity_df, _two_variable_sensitivity_display_df 12 | from sensitivity.hexbin import _hex_figure_from_sensitivity_df 13 | 14 | 15 | @dataclass 16 | class SensitivityAnalyzer: 17 | """ 18 | Runs sensitivity analysis based on the passed function and possible values for each argument. 19 | 20 | Runs func with the cartesian product of the possible values for each argument, passed 21 | in sensitivity_values. Exposes the DataFrame containing the results, a styled version of 22 | the DataFrame, and a Hex-Bin plot. 23 | 24 | :param sensitivity_values: Dictionary where keys are func's argument names and values are lists of possible 25 | values to use for that argument. 26 | :param func: Function that accepts arguments with names matching the keys of sensitivity_values, and outputs a 27 | scalar value. 28 | :param result_name: Name for result shown in graph color bar label 29 | :param agg_func: If there are multiple results within the hex parameter area, function to aggregate those results to 30 | get a single value for the hex area. The function should accept a sequence of values and return a scalar. 31 | :param reverse_colors: Default is for red to represent low values of result, green for high values. Set 32 | reverse_colors=True to have green represent low values of result and red for high values. 33 | :param grid_size: Number of hex bins on each axis. E.g. passing 5 would create a 5x5 grid, 25 hex bins. 34 | :param func_kwargs: Additional arguments to pass to func, regardless of the sensitivity values picked 35 | :param num_fmt: used to apply additional styling to DataFrames. Should be a number format string in the 36 | same style as would be passed to df.style.format, e.g. '${:,.2f}' for USD formatting 37 | :param color_map: matplotlib color map, default is RdYlGn (red, yellow, green). See 38 | https://matplotlib.org/3.3.2/tutorials/colors/colormaps.html 39 | :param labels: Optional dictionary where keys are arguments of the function and values are the displayed names 40 | for these arguments in the styled DataFrames and plots 41 | :return: Sensitivity analysis hex bin sub plot figure 42 | 43 | Examples: 44 | >>> from sensitivity import SensitivityAnalyzer 45 | >>> 46 | >>> # Some example function 47 | >>> def add_5_to_values(value1, value2): 48 | >>> return value1 + value2 + 5 49 | >>> 50 | >>> # The values to be passed for each parameter of the function 51 | >>> sensitivity_values = { 52 | >>> 'value1': [1, 2, 3], 53 | >>> 'value2': [4, 5, 6], 54 | >>> } 55 | >>> 56 | >>> sa = SensitivityAnalyzer( 57 | >>> sensitivity_values, 58 | >>> add_5_to_values 59 | >>> ) 60 | >>> 61 | >>> # Plain DataFrame Containing Values 62 | >>> sa.df 63 | >>> 64 | >>> # Styled DataFrame 65 | >>> sa.styled_dfs() 66 | >>> 67 | >>> # Hex-Bin Plot 68 | >>> sa.plot() 69 | """ 70 | sensitivity_values: Dict[str, Any] 71 | func: Callable 72 | 73 | result_name: str = 'Result' 74 | agg_func: Callable = np.mean 75 | reverse_colors: bool = False 76 | grid_size: int = 8 77 | func_kwargs_dict: Optional[Dict[str, Any]] = None 78 | num_fmt: Optional[str] = None 79 | color_map: str = 'RdYlGn' 80 | labels: Optional[Dict[str, str]] = None 81 | 82 | def __post_init__(self): 83 | if self.func_kwargs_dict is None: 84 | self.func_kwargs_dict = {} 85 | self.df = sensitivity_df( 86 | self.sensitivity_values, 87 | self.func, 88 | result_name=self.result_name, 89 | labels=self.labels, 90 | **self.func_kwargs_dict 91 | ) 92 | 93 | def plot(self, **kwargs) -> plt.Figure: 94 | """ 95 | Creates hex-bin plots of the sensitivity analysis results 96 | 97 | :param kwargs: agg_func, reverse_colors, grid_size, color_map (see :py:class:`.SensitivityAnalyzer`) 98 | :return: Matplotlib Figure containing one or more plots of sensitivity analysis results 99 | """ 100 | config_dict: Dict[str, Any] = dict( 101 | agg_func=self.agg_func, 102 | reverse_colors=self.reverse_colors, 103 | grid_size=self.grid_size, 104 | color_map=self.color_map, 105 | ) 106 | config_dict.update(**kwargs) 107 | sensitivity_cols = self.sensitivity_cols 108 | return _hex_figure_from_sensitivity_df( 109 | self.df, 110 | sensitivity_cols, 111 | result_name=self.result_name, 112 | **config_dict 113 | ) 114 | 115 | def styled_dfs(self, disp: bool = True, **kwargs) -> Union[Styler, Dict[Sequence[str], Styler]]: 116 | """ 117 | Creates Pandas Styler objects showing a gradient over the sensitivity results 118 | 119 | :param disp: Whether to display the Styler objects before returning 120 | :param kwargs: reverse_colors, agg_func, num_fmt, color_map (see :py:class:`.SensitivityAnalyzer`) 121 | :return: 122 | """ 123 | output = {} 124 | config_dict: Dict[str, Any] = dict( 125 | reverse_colors=self.reverse_colors, 126 | agg_func=self.agg_func, 127 | num_fmt=self.num_fmt, 128 | color_map=self.color_map, 129 | ) 130 | config_dict.update(**kwargs) 131 | # Output a single Styler if only one or two variables 132 | sensitivity_cols = self.sensitivity_cols 133 | if len(sensitivity_cols) == 1: 134 | output[tuple(sensitivity_cols)] = _style_sensitivity_df( 135 | self.df, 136 | sensitivity_cols[0], 137 | reverse_colors=config_dict['reverse_colors'], 138 | col_subset=[self.result_name], 139 | result_col=self.result_name, 140 | num_fmt=config_dict['num_fmt'], 141 | color_map=config_dict['color_map'], 142 | ) 143 | elif len(sensitivity_cols) == 2: 144 | col1 = sensitivity_cols[0] 145 | col2 = sensitivity_cols[1] 146 | df = _two_variable_sensitivity_display_df( 147 | self.df, 148 | col1, 149 | col2, 150 | result_col=self.result_name, 151 | agg_func=config_dict['agg_func'] 152 | ) 153 | output[(col1, col2)] = _style_sensitivity_df( 154 | df, 155 | col1, 156 | col2=col2, 157 | reverse_colors=config_dict['reverse_colors'], 158 | result_col=self.result_name, 159 | num_fmt=config_dict['num_fmt'], 160 | color_map=config_dict['color_map'], 161 | ) 162 | elif len(sensitivity_cols) > 2: 163 | # Need to output multiple, one for each pair of variables 164 | for col1, col2 in itertools.combinations(sensitivity_cols, 2): 165 | df = _two_variable_sensitivity_display_df( 166 | self.df, 167 | col1, 168 | col2, 169 | result_col=self.result_name 170 | ) 171 | output[(col1, col2)] = (_style_sensitivity_df( 172 | df, 173 | col1, 174 | col2=col2, 175 | reverse_colors=config_dict['reverse_colors'], 176 | result_col=self.result_name, 177 | num_fmt=config_dict['num_fmt'], 178 | color_map=config_dict['color_map'], 179 | )) 180 | elif len(sensitivity_cols) == 0: 181 | raise ValueError('must pass sensitivity columns') 182 | 183 | if disp: 184 | for var_tup, sens_df in output.items(): 185 | var_str = ' vs. '.join(var_tup) 186 | title_str = f'{self.result_name} by {var_str}' 187 | _display_header(title_str) 188 | display(HTML(sens_df.to_html())) 189 | 190 | if len(output) == 1: 191 | return list(output.values())[0] # get Styler object out of dictionary 192 | 193 | return output 194 | 195 | @property 196 | def sensitivity_cols(self) -> List[str]: 197 | sensitivity_cols = list(self.sensitivity_values.keys()) 198 | if self.labels: 199 | new_sensitivity_cols: List[str] = [] 200 | for col in sensitivity_cols: 201 | if col in self.labels: 202 | new_sensitivity_cols.append(self.labels[col]) 203 | else: 204 | new_sensitivity_cols.append(col) 205 | sensitivity_cols = new_sensitivity_cols 206 | return sensitivity_cols 207 | 208 | 209 | def _display_header(text: str): 210 | html_str = f'

{text}

' 211 | display(HTML(html_str)) -------------------------------------------------------------------------------- /docsrc/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # dero documentation build configuration file, created by 5 | # sphinx-quickstart on Sat Aug 3 16:59:37 2019. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import pathlib 22 | import sys 23 | import datetime 24 | import warnings 25 | import sphinx_rtd_theme 26 | # sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__name__), '..'))) 27 | 28 | sys.path.insert(0, os.path.abspath('../..')) 29 | import conf 30 | import version as vs 31 | from docsrc.directives.auto_summary import AutoSummaryNameOnly 32 | 33 | # -- General configuration ------------------------------------------------ 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | # needs_sphinx = '1.0' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.todo', 45 | 'sphinx.ext.coverage', 46 | 'sphinx.ext.mathjax', 47 | 'sphinx.ext.ifconfig', 48 | 'sphinx.ext.viewcode', 49 | 'sphinx.ext.autosummary', 50 | 'sphinx.ext.doctest', 51 | 'sphinx.ext.intersphinx', 52 | 'sphinx_autodoc_typehints', 53 | 'sphinx_paramlinks', 54 | 'sphinx_rtd_theme', 55 | 'sphinx_gallery.gen_gallery', 56 | 'sphinx_copybutton', 57 | 'sphinx_sitemap' 58 | ] 59 | 60 | # Options for sphinx_autodoc_typehints 61 | set_type_checking_flag = False 62 | 63 | # Options for sphinx.ext.autosummary 64 | autodoc_default_flags = ['members'] 65 | autosummary_generate = True 66 | autodoc_mock_imports = conf.PACKAGE_INSTALL_REQUIRES + conf.DOCS_OTHER_MOCK_IMPORTS 67 | 68 | # Add any paths that contain templates here, relative to this directory. 69 | templates_path = ['_templates'] 70 | 71 | # The suffix(es) of source filenames. 72 | # You can specify multiple suffix as a list of string: 73 | # 74 | # source_suffix = ['.rst', '.md'] 75 | source_suffix = '.rst' 76 | 77 | # The master toctree document. 78 | master_doc = 'index' 79 | 80 | # General information about the project. 81 | project = conf.PACKAGE_NAME 82 | copyright = f'{datetime.datetime.now().year}, {conf.PACKAGE_AUTHOR}' 83 | author = conf.PACKAGE_AUTHOR 84 | 85 | # The version info for the project you're documenting, acts as replacement for 86 | # |version| and |release|, also used in various other places throughout the 87 | # built documents. 88 | # 89 | # The short X.Y version. 90 | version = vs.__version__ 91 | # The full version, including alpha/beta/rc tags. 92 | release = vs.__version__ 93 | 94 | # The language for content autogenerated by Sphinx. Refer to documentation 95 | # for a list of supported languages. 96 | # 97 | # This is also used if you do content translation via gettext catalogs. 98 | # Usually you set "language" from the command line for these cases. 99 | language = None 100 | 101 | # List of patterns, relative to source directory, that match files and 102 | # directories to ignore when looking for source files. 103 | # This patterns also effect to html_static_path and html_extra_path 104 | exclude_patterns = ['**.ipynb_checkpoints'] 105 | 106 | # The name of the Pygments (syntax highlighting) style to use. 107 | pygments_style = 'sphinx' 108 | 109 | # If true, `todo` and `todoList` produce output, else they produce nothing. 110 | todo_include_todos = True 111 | 112 | # Base URL for sitemap 113 | html_baseurl = conf.PACKAGE_URLS['Documentation'] + '/' 114 | 115 | sphinx_gallery_conf = { 116 | 'examples_dirs': '../../_examples', # path to your example scripts 117 | 'gallery_dirs': 'auto_examples', # path to where to save gallery generated output 118 | 'filename_pattern': '/', # re to match examples .py files that should be run to generate output. Set as / for all 119 | 'reference_url': { 120 | # The module you locally document uses None 121 | 'sphinx_gallery': None, 122 | }, 123 | 'binder': { 124 | # Required keys 125 | 'org': conf.REPO_USERNAME, 126 | 'repo': conf.REPO_NAME, 127 | 'branch': 'gh-pages', # Can be any branch, tag, or commit hash. Use a branch that hosts your docs. 128 | 'binderhub_url': 'https://mybinder.org', # Any URL of a binderhub deployment. Must be full URL (e.g. https://mybinder.org). 129 | 'dependencies': './binder/requirements.txt', 130 | # Optional keys 131 | # 'filepath_prefix': '', # A prefix to prepend to any filepaths in Binder links. 132 | # 'notebooks_dir': '', # Jupyter notebooks for Binder will be copied to this directory (relative to built documentation root). 133 | 'use_jupyter_lab': True, # Whether Binder links should start Jupyter Lab instead of the Jupyter Notebook interface. 134 | } 135 | } 136 | 137 | intersphinx_mapping = { 138 | 'python': ('https://docs.python.org/3', None), 139 | 'numpy': ('https://docs.scipy.org/doc/numpy/', None), 140 | 'matplotlib': ('https://matplotlib.org', None) 141 | } 142 | 143 | # Remove matplotlib agg warnings from generated doc when using plt.show 144 | warnings.filterwarnings("ignore", category=UserWarning, 145 | message='Matplotlib is currently using agg, which is a' 146 | ' non-GUI backend, so cannot show the figure.') 147 | 148 | # -- Options for HTML output ---------------------------------------------- 149 | 150 | # The theme to use for HTML and HTML Help pages. See the documentation for 151 | # a list of builtin themes. 152 | # 153 | html_theme = 'sphinx_rtd_theme' 154 | 155 | # Theme options are theme-specific and customize the look and feel of a theme 156 | # further. For a list of options available for each theme, see the 157 | # documentation. 158 | # 159 | html_theme_options = {} 160 | if conf.GOOGLE_ANALYTICS_TRACKING_ID: 161 | html_theme_options['analytics_id'] = conf.GOOGLE_ANALYTICS_TRACKING_ID 162 | 163 | # Add any paths that contain custom static files (such as style sheets) here, 164 | # relative to this directory. They are copied after the builtin static files, 165 | # so a file named "default.css" will overwrite the builtin "default.css". 166 | html_static_path = ['_static'] 167 | 168 | # Custom sidebar templates, must be a dictionary that maps document names 169 | # to template names. 170 | # 171 | # This is required for the alabaster theme 172 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 173 | html_sidebars = { 174 | '**': [ 175 | 'localtoc.html', 176 | 'relations.html', # needs 'show_related': True theme option to display 177 | 'searchbox.html', 178 | ] 179 | } 180 | 181 | # The name of an image file (relative to this directory) to place at the top 182 | # of the sidebar. 183 | # 184 | 185 | # Uncomment the following line once logo url is set in main conf.py 186 | # html_logo = str(pathlib.Path('_static') / 'images' / 'logo.svg') 187 | 188 | 189 | # The name of an image file (relative to this directory) to use as a favicon of 190 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 191 | # pixels large. 192 | # 193 | 194 | # Uncomment the following line once logo url is set in main conf.py 195 | # html_favicon = str(pathlib.Path('_static') / 'images' / 'logo.svg') 196 | 197 | 198 | # -- Options for HTMLHelp output ------------------------------------------ 199 | 200 | # Output file base name for HTML help builder. 201 | htmlhelp_basename = 'helpdoc' 202 | 203 | 204 | # -- Options for LaTeX output --------------------------------------------- 205 | 206 | latex_elements = { 207 | # The paper size ('letterpaper' or 'a4paper'). 208 | # 209 | # 'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | # 213 | # 'pointsize': '10pt', 214 | 215 | # Additional stuff for the LaTeX preamble. 216 | # 217 | # 'preamble': '', 218 | 219 | # Latex figure (float) alignment 220 | # 221 | # 'figure_align': 'htbp', 222 | } 223 | 224 | # Grouping the document tree into LaTeX files. List of tuples 225 | # (source start file, target name, title, 226 | # author, documentclass [howto, manual, or own class]). 227 | latex_documents = [ 228 | (master_doc, f'{conf.PACKAGE_NAME}.tex', f'{conf.PACKAGE_NAME} Documentation', 229 | conf.PACKAGE_AUTHOR, 'manual'), 230 | ] 231 | 232 | 233 | # -- Options for manual page output --------------------------------------- 234 | 235 | # One entry per manual page. List of tuples 236 | # (source start file, name, description, authors, manual section). 237 | man_pages = [ 238 | (master_doc, conf.PACKAGE_NAME, f'{conf.PACKAGE_NAME} Documentation', 239 | [author], 1) 240 | ] 241 | 242 | 243 | # -- Options for Texinfo output ------------------------------------------- 244 | 245 | # Grouping the document tree into Texinfo files. List of tuples 246 | # (source start file, target name, title, author, 247 | # dir menu entry, description, category) 248 | texinfo_documents = [ 249 | (master_doc, conf.PACKAGE_NAME, f'{conf.PACKAGE_NAME} Documentation', 250 | author, conf.PACKAGE_NAME, conf.PACKAGE_SHORT_DESCRIPTION, 251 | 'Miscellaneous'), 252 | ] 253 | 254 | 255 | def skip(app, what, name, obj, would_skip, options): 256 | if name == "__init__": 257 | return False 258 | return would_skip 259 | 260 | 261 | def setup(app): 262 | app.connect("autodoc-skip-member", skip) 263 | app.add_directive('autosummarynameonly', AutoSummaryNameOnly) 264 | app.add_css_file('custom.css') -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "1cce6ac7e07d13878ced9ae35300a29128f810a4bdb10a78e0cb80e8df008555" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "alabaster": { 18 | "hashes": [ 19 | "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", 20 | "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" 21 | ], 22 | "version": "==0.7.12" 23 | }, 24 | "argon2-cffi": { 25 | "hashes": [ 26 | "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80", 27 | "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b" 28 | ], 29 | "markers": "python_version >= '3.6'", 30 | "version": "==21.3.0" 31 | }, 32 | "argon2-cffi-bindings": { 33 | "hashes": [ 34 | "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", 35 | "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f", 36 | "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", 37 | "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194", 38 | "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", 39 | "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a", 40 | "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", 41 | "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5", 42 | "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", 43 | "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7", 44 | "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", 45 | "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", 46 | "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", 47 | "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", 48 | "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", 49 | "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", 50 | "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", 51 | "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", 52 | "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", 53 | "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", 54 | "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351" 55 | ], 56 | "markers": "python_version >= '3.6'", 57 | "version": "==21.2.0" 58 | }, 59 | "arrow": { 60 | "hashes": [ 61 | "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1", 62 | "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2" 63 | ], 64 | "markers": "python_version >= '3.6'", 65 | "version": "==1.2.3" 66 | }, 67 | "asttokens": { 68 | "hashes": [ 69 | "sha256:c61e16246ecfb2cde2958406b4c8ebc043c9e6d73aaa83c941673b35e5d3a76b", 70 | "sha256:e3305297c744ae53ffa032c45dc347286165e4ffce6875dc662b205db0623d86" 71 | ], 72 | "version": "==2.0.8" 73 | }, 74 | "attrs": { 75 | "hashes": [ 76 | "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", 77 | "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" 78 | ], 79 | "markers": "python_version >= '3.5'", 80 | "version": "==22.1.0" 81 | }, 82 | "babel": { 83 | "hashes": [ 84 | "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51", 85 | "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb" 86 | ], 87 | "markers": "python_version >= '3.6'", 88 | "version": "==2.10.3" 89 | }, 90 | "backcall": { 91 | "hashes": [ 92 | "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", 93 | "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" 94 | ], 95 | "version": "==0.2.0" 96 | }, 97 | "beautifulsoup4": { 98 | "hashes": [ 99 | "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", 100 | "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" 101 | ], 102 | "markers": "python_version >= '3.6'", 103 | "version": "==4.11.1" 104 | }, 105 | "binaryornot": { 106 | "hashes": [ 107 | "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061", 108 | "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4" 109 | ], 110 | "version": "==0.4.4" 111 | }, 112 | "bleach": { 113 | "hashes": [ 114 | "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a", 115 | "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c" 116 | ], 117 | "markers": "python_version >= '3.7'", 118 | "version": "==5.0.1" 119 | }, 120 | "bs4": { 121 | "hashes": [ 122 | "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a" 123 | ], 124 | "index": "pypi", 125 | "version": "==0.0.1" 126 | }, 127 | "certifi": { 128 | "hashes": [ 129 | "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", 130 | "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" 131 | ], 132 | "markers": "python_version >= '3.6'", 133 | "version": "==2022.9.24" 134 | }, 135 | "cffi": { 136 | "hashes": [ 137 | "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", 138 | "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", 139 | "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", 140 | "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", 141 | "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", 142 | "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", 143 | "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", 144 | "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", 145 | "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", 146 | "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", 147 | "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", 148 | "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", 149 | "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", 150 | "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", 151 | "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", 152 | "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", 153 | "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", 154 | "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", 155 | "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", 156 | "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", 157 | "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", 158 | "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", 159 | "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", 160 | "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", 161 | "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", 162 | "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", 163 | "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", 164 | "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", 165 | "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", 166 | "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", 167 | "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", 168 | "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", 169 | "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", 170 | "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", 171 | "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", 172 | "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", 173 | "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", 174 | "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", 175 | "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", 176 | "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", 177 | "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", 178 | "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", 179 | "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", 180 | "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", 181 | "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", 182 | "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", 183 | "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", 184 | "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", 185 | "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", 186 | "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", 187 | "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", 188 | "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", 189 | "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", 190 | "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", 191 | "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", 192 | "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", 193 | "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", 194 | "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", 195 | "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", 196 | "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", 197 | "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", 198 | "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", 199 | "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", 200 | "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" 201 | ], 202 | "version": "==1.15.1" 203 | }, 204 | "chardet": { 205 | "hashes": [ 206 | "sha256:0368df2bfd78b5fc20572bb4e9bb7fb53e2c094f60ae9993339e8671d0afb8aa", 207 | "sha256:d3e64f022d254183001eccc5db4040520c0f23b1a3f33d6413e099eb7f126557" 208 | ], 209 | "markers": "python_version >= '3.6'", 210 | "version": "==5.0.0" 211 | }, 212 | "charset-normalizer": { 213 | "hashes": [ 214 | "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", 215 | "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" 216 | ], 217 | "markers": "python_version >= '3.6'", 218 | "version": "==2.1.1" 219 | }, 220 | "click": { 221 | "hashes": [ 222 | "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", 223 | "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" 224 | ], 225 | "markers": "python_version >= '3.7'", 226 | "version": "==8.1.3" 227 | }, 228 | "colorama": { 229 | "hashes": [ 230 | "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", 231 | "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" 232 | ], 233 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 234 | "version": "==0.4.5" 235 | }, 236 | "commonmark": { 237 | "hashes": [ 238 | "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", 239 | "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" 240 | ], 241 | "version": "==0.9.1" 242 | }, 243 | "contourpy": { 244 | "hashes": [ 245 | "sha256:0389349875424aa8c5e61f757e894687916bc4e9616cc6afcbd8051aa2428952", 246 | "sha256:0395ae71164bfeb2dedd136e03c71a2718a5aa9873a46f518f4133be0d63e1d2", 247 | "sha256:057114f698ffb9e54657e8fda6802e2f5c8fad609845cf6afaf31590ef6a33c0", 248 | "sha256:061e1f066c419ffe25b615a1df031b4832ea1d7f2676937e69e8e00e24512005", 249 | "sha256:06c4d1dde5ee4f909a8a95ba1eb04040c6c26946b4f3b5beaf10d45f14e940ee", 250 | "sha256:09ed9b63f4df8a7591b7a4a26c1ad066dcaafda1f846250fdcb534074a411692", 251 | "sha256:0f7672148f8fca48e4efc16aba24a7455b40c22d4f8abe42475dec6a12b0bb9a", 252 | "sha256:0f89f0608a5aa8142ed0e53957916623791a88c7f5e5f07ae530c328beeb888f", 253 | "sha256:128bd7acf569f8443ad5b2227f30ac909e4f5399ed221727eeacf0c6476187e6", 254 | "sha256:19ea64fa0cf389d2ebc10974616acfa1fdecbd73d1fd9c72215b782f3c40f561", 255 | "sha256:1fb782982c42cee667b892a0b0c52a9f6c7ecf1da5c5f4345845f04eaa862f93", 256 | "sha256:218722a29c5c26677d37c44f5f8a372daf6f07870aad793a97d47eb6ad6b3290", 257 | "sha256:2b5e334330d82866923015b455260173cb3b9e3b4e297052d758abd262031289", 258 | "sha256:2bf5c846c257578b03d498b20f54f53551616a507d8e5463511c58bb58e9a9cf", 259 | "sha256:2d0ad9a85f208473b1f3613c45756c7aa6fcc288266a8c7b873f896aaf741b6b", 260 | "sha256:2f54dcc9bb9390fd0636301ead134d46d5229fe86da0db4d974c0fda349f560e", 261 | "sha256:3109fa601d2a448cec4643abd3a31f972bf05b7c2f2e83df9d3429878f8c10ae", 262 | "sha256:3210d93ad2af742b6a96cf39792f7181822edbb8fe11c3ef29d1583fe637a8d8", 263 | "sha256:3b3082ade8849130203d461b98c2a061b382c46074b43b4edd5cefd81af92b8a", 264 | "sha256:3c3f2f6b898a40207843ae01970e57e33d22a26b22f23c6a5e07b4716751085f", 265 | "sha256:3ca40d7844b391d90b864c6a6d1bb6b88b09035fb4d866d64d43c4d26fb0ab64", 266 | "sha256:3cfc067ddde78b76dcbc9684d82688b7d3c5158fa2254a085f9bcb9586c1e2d8", 267 | "sha256:434942fa2f9019b9ae525fb752dc523800c49a1a28fbd6d9240b0fa959573dcc", 268 | "sha256:46b8e24813e2fb5a3e598c1f8b9ae403e1438cb846a80cc2b33cddf19dddd7f2", 269 | "sha256:59c827e536bb5e3ef58e06da0faba61fd89a14f30b68bcfeca41f43ca83a1942", 270 | "sha256:60f37acd4e4227c5a29f737d9a85ca3145c529a8dd4bf70af7f0637c61b49222", 271 | "sha256:689d7d2a840619915d0abd1ecc6e399fee202f8ad315acda2807f4ca420d0802", 272 | "sha256:6c02e22cf09996194bcb3a4784099975cf527d5c29caf759abadf29ebdb2fe27", 273 | "sha256:79908b9d02b1d6c1c71ff3b7ad127f3f82e14a8e091ab44b3c7e34b649fea733", 274 | "sha256:7c9e99aac7b430f6a9f15eebf058c742097cea3369f23a2bfc5e64d374b67e3a", 275 | "sha256:813c2944e940ef8dccea71305bacc942d4b193a021140874b3e58933ec44f5b6", 276 | "sha256:87121b9428ac568fb84fae4af5e7852fc34f02eadc4e3e91f6c8989327692186", 277 | "sha256:896631cd40222aef3697e4e51177d14c3709fda49d30983269d584f034acc8a4", 278 | "sha256:970a4be7ec84ccda7c27cb4ae74930bbbd477bc8d849ed55ea798084dd5fca8c", 279 | "sha256:9939796abcadb2810a63dfb26ff8ca4595fe7dd70a3ceae7f607a2639b714307", 280 | "sha256:99a8071e351b50827ad976b92ed91845fb614ac67a3c41109b24f3d8bd3afada", 281 | "sha256:9c16fa267740d67883899e054cccb4279e002f3f4872873b752c1ba15045ff49", 282 | "sha256:a30e95274f5c0e007ccc759ec258aa5708c534ec058f153ee25ac700a2f1438b", 283 | "sha256:a74afd8d560eaafe0d9e3e1db8c06081282a05ca4de00ee416195085a79d7d3d", 284 | "sha256:b46a04588ceb7cf132568e0e564a854627ef87a1ed3bf536234540a79ced44b0", 285 | "sha256:b4963cf08f4320d98ae72ec7694291b8ab85cb7da3b0cd824bc32701bc992edf", 286 | "sha256:b50e481a4317a8efcfffcfddcd4c9b36eacba440440e70cbe0256aeb6fd6abae", 287 | "sha256:b85553699862c09937a7a5ea14ee6229087971a7d51ae97d5f4b407f571a2c17", 288 | "sha256:bcc98d397c3dea45d5b262029564b29cb8e945f2607a38bee6163694c0a8b4ef", 289 | "sha256:bed3a2a823a041e8d249b1a7ec132933e1505299329b5cfe1b2b5ec689ec7675", 290 | "sha256:bf6b4c0c723664f65c2a47c8cb6ebbf660b0b2e2d936adf2e8503d4e93359465", 291 | "sha256:bfd634cb9685161b2a51f73a7fc4736fd0d67a56632d52319317afaa27f08243", 292 | "sha256:c0d5ee865b5fd16bf62d72122aadcc90aab296c30c1adb0a32b4b66bd843163e", 293 | "sha256:c2b4eab7c12f9cb460509bc34a3b086f9802f0dba27c89a63df4123819ad64af", 294 | "sha256:c51568e94f7f232296de30002f2a50f77a7bd346673da3e4f2aaf9d2b833f2e5", 295 | "sha256:c5158616ab39d34b76c50f40c81552ee180598f7825dc7a66fd187d29958820f", 296 | "sha256:cdacddb18d55ffec42d1907079cdc04ec4fa8a990cdf5b9d9fe67d281fc0d12e", 297 | "sha256:ce763369e646e59e4ca2c09735cd1bdd3048d909ad5f2bc116e83166a9352f3c", 298 | "sha256:d45822b0a2a452327ab4f95efe368d234d5294bbf89a99968be27c7938a21108", 299 | "sha256:d8150579bf30cdf896906baf256aa200cd50dbe6e565c17d6fd3d678e21ff5de", 300 | "sha256:d88814befbd1433152c5f6dd536905149ba028d795a22555b149ae0a36024d9e", 301 | "sha256:dca5be83a6dfaf933a46e3bc2b9f2685e5ec61b22f6a38ad740aac9c16e9a0ff", 302 | "sha256:dd084459ecdb224e617e4ab3f1d5ebe4d1c48facb41f24952b76aa6ba9712bb0", 303 | "sha256:def9a01b73c9e27d70ea03b381fb3e7aadfac1f398dbd63751313c3a46747ef5", 304 | "sha256:df65f4b2b4e74977f0336bef12a88051ab24e6a16873cd9249f34d67cb3e345d", 305 | "sha256:dfe924e5a63861c82332a12adeeab955dc8c8009ddbbd80cc2fcca049ff89a49", 306 | "sha256:e67dcaa34dcd908fcccbf49194211d847c731b6ebaac661c1c889f1bf6af1e44", 307 | "sha256:eba62b7c21a33e72dd8adab2b92dd5610d8527f0b2ac28a8e0770e71b21a13f9", 308 | "sha256:ed9c91bf4ce614efed5388c3f989a7cfe08728ab871d995a486ea74ff88993db", 309 | "sha256:f05d311c937da03b0cd26ac3e14cb991f6ff8fc94f98b3df9713537817539795", 310 | "sha256:f1cc623fd6855b25da52b3275e0c9e51711b86a9dccc75f8c9ab4432fd8e42c7", 311 | "sha256:f670686d99c867d0f24b28ce8c6f02429c6eef5e2674aab287850d0ee2d20437", 312 | "sha256:f856652f9b533c6cd2b9ad6836a7fc0e43917d7ff15be46c5baf1350f8cdc5d9", 313 | "sha256:fb0458d74726937ead9e2effc91144aea5a58ecee9754242f8539a782bed685a" 314 | ], 315 | "markers": "python_version >= '3.7'", 316 | "version": "==1.0.5" 317 | }, 318 | "cookiecutter": { 319 | "hashes": [ 320 | "sha256:9f3ab027cec4f70916e28f03470bdb41e637a3ad354b4d65c765d93aad160022", 321 | "sha256:f3982be8d9c53dac1261864013fdec7f83afd2e42ede6f6dd069c5e149c540d5" 322 | ], 323 | "markers": "python_version >= '3.7'", 324 | "version": "==2.1.1" 325 | }, 326 | "coverage": { 327 | "extras": [ 328 | "toml" 329 | ], 330 | "hashes": [ 331 | "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79", 332 | "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a", 333 | "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f", 334 | "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a", 335 | "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa", 336 | "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398", 337 | "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba", 338 | "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d", 339 | "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf", 340 | "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b", 341 | "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518", 342 | "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d", 343 | "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795", 344 | "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2", 345 | "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e", 346 | "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32", 347 | "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745", 348 | "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b", 349 | "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e", 350 | "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d", 351 | "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f", 352 | "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660", 353 | "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62", 354 | "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6", 355 | "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04", 356 | "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c", 357 | "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5", 358 | "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef", 359 | "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc", 360 | "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae", 361 | "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578", 362 | "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466", 363 | "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4", 364 | "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91", 365 | "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0", 366 | "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4", 367 | "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b", 368 | "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe", 369 | "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b", 370 | "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75", 371 | "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b", 372 | "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c", 373 | "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72", 374 | "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b", 375 | "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f", 376 | "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e", 377 | "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53", 378 | "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3", 379 | "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84", 380 | "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987" 381 | ], 382 | "markers": "python_version >= '3.7'", 383 | "version": "==6.5.0" 384 | }, 385 | "cruft": { 386 | "hashes": [ 387 | "sha256:36026426d1d602656fe894e027dcd502ccf48da4b30927e071928e35465c2676", 388 | "sha256:deae3ee097f091462d7c872df22d6ad0ac89ce870bd1665e716e0d08fd21357b" 389 | ], 390 | "index": "pypi", 391 | "version": "==2.11.1" 392 | }, 393 | "cryptography": { 394 | "hashes": [ 395 | "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", 396 | "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", 397 | "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", 398 | "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", 399 | "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", 400 | "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", 401 | "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", 402 | "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", 403 | "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", 404 | "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", 405 | "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", 406 | "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", 407 | "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", 408 | "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", 409 | "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", 410 | "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", 411 | "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", 412 | "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", 413 | "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", 414 | "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", 415 | "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", 416 | "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", 417 | "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", 418 | "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", 419 | "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", 420 | "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" 421 | ], 422 | "markers": "python_version >= '3.6'", 423 | "version": "==38.0.1" 424 | }, 425 | "cycler": { 426 | "hashes": [ 427 | "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3", 428 | "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f" 429 | ], 430 | "markers": "python_version >= '3.6'", 431 | "version": "==0.11.0" 432 | }, 433 | "debugpy": { 434 | "hashes": [ 435 | "sha256:34d2cdd3a7c87302ba5322b86e79c32c2115be396f3f09ca13306d8a04fe0f16", 436 | "sha256:3c9f985944a30cfc9ae4306ac6a27b9c31dba72ca943214dad4a0ab3840f6161", 437 | "sha256:4e255982552b0edfe3a6264438dbd62d404baa6556a81a88f9420d3ed79b06ae", 438 | "sha256:5ad571a36cec137ae6ed951d0ff75b5e092e9af6683da084753231150cbc5b25", 439 | "sha256:6efc30325b68e451118b795eff6fe8488253ca3958251d5158106d9c87581bc6", 440 | "sha256:7c302095a81be0d5c19f6529b600bac971440db3e226dce85347cc27e6a61908", 441 | "sha256:84c39940a0cac410bf6aa4db00ba174f973eef521fbe9dd058e26bcabad89c4f", 442 | "sha256:86d784b72c5411c833af1cd45b83d80c252b77c3bfdb43db17c441d772f4c734", 443 | "sha256:adcfea5ea06d55d505375995e150c06445e2b20cd12885bcae566148c076636b", 444 | "sha256:b8deaeb779699350deeed835322730a3efec170b88927debc9ba07a1a38e2585", 445 | "sha256:c4b2bd5c245eeb49824bf7e539f95fb17f9a756186e51c3e513e32999d8846f3", 446 | "sha256:c4cd6f37e3c168080d61d698390dfe2cd9e74ebf80b448069822a15dadcda57d", 447 | "sha256:cca23cb6161ac89698d629d892520327dd1be9321c0960e610bbcb807232b45d", 448 | "sha256:d5c814596a170a0a58fa6fad74947e30bfd7e192a5d2d7bd6a12156c2899e13a", 449 | "sha256:daadab4403427abd090eccb38d8901afd8b393e01fd243048fab3f1d7132abb4", 450 | "sha256:dda8652520eae3945833e061cbe2993ad94a0b545aebd62e4e6b80ee616c76b2", 451 | "sha256:e8922090514a890eec99cfb991bab872dd2e353ebb793164d5f01c362b9a40bf", 452 | "sha256:fc233a0160f3b117b20216f1169e7211b83235e3cd6749bcdd8dbb72177030c7" 453 | ], 454 | "markers": "python_version >= '3.7'", 455 | "version": "==1.6.3" 456 | }, 457 | "decorator": { 458 | "hashes": [ 459 | "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", 460 | "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" 461 | ], 462 | "markers": "python_version >= '3.5'", 463 | "version": "==5.1.1" 464 | }, 465 | "defusedxml": { 466 | "hashes": [ 467 | "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", 468 | "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" 469 | ], 470 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 471 | "version": "==0.7.1" 472 | }, 473 | "docutils": { 474 | "hashes": [ 475 | "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", 476 | "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" 477 | ], 478 | "markers": "python_version >= '3.7'", 479 | "version": "==0.19" 480 | }, 481 | "entrypoints": { 482 | "hashes": [ 483 | "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4", 484 | "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f" 485 | ], 486 | "markers": "python_version >= '3.6'", 487 | "version": "==0.4" 488 | }, 489 | "executing": { 490 | "hashes": [ 491 | "sha256:2c2c07d1ec4b2d8f9676b25170f1d8445c0ee2eb78901afb075a4b8d83608c6a", 492 | "sha256:4a6d96ba89eb3dcc11483471061b42b9006d8c9f81c584dd04246944cd022530" 493 | ], 494 | "version": "==1.1.0" 495 | }, 496 | "fastjsonschema": { 497 | "hashes": [ 498 | "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18", 499 | "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c" 500 | ], 501 | "version": "==2.16.2" 502 | }, 503 | "fonttools": { 504 | "hashes": [ 505 | "sha256:86918c150c6412798e15a0de6c3e0d061ddefddd00f97b4f7b43dfa867ad315e", 506 | "sha256:afae1b39555f9c3f0ad1f0f1daf678e5ad157e38c8842ecb567951bf1a9b9fd7" 507 | ], 508 | "markers": "python_version >= '3.7'", 509 | "version": "==4.37.4" 510 | }, 511 | "gitdb": { 512 | "hashes": [ 513 | "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd", 514 | "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa" 515 | ], 516 | "markers": "python_version >= '3.6'", 517 | "version": "==4.0.9" 518 | }, 519 | "gitpython": { 520 | "hashes": [ 521 | "sha256:6bd3451b8271132f099ceeaf581392eaf6c274af74bb06144307870479d0697c", 522 | "sha256:77bfbd299d8709f6af7e0c70840ef26e7aff7cf0c1ed53b42dd7fc3a310fcb02" 523 | ], 524 | "markers": "python_version >= '3.7'", 525 | "version": "==3.1.28" 526 | }, 527 | "idna": { 528 | "hashes": [ 529 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 530 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 531 | ], 532 | "markers": "python_version >= '3.5'", 533 | "version": "==3.4" 534 | }, 535 | "imagesize": { 536 | "hashes": [ 537 | "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", 538 | "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" 539 | ], 540 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 541 | "version": "==1.4.1" 542 | }, 543 | "importlib-metadata": { 544 | "hashes": [ 545 | "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab", 546 | "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43" 547 | ], 548 | "markers": "python_version >= '3.7'", 549 | "version": "==5.0.0" 550 | }, 551 | "importlib-resources": { 552 | "hashes": [ 553 | "sha256:5481e97fb45af8dcf2f798952625591c58fe599d0735d86b10f54de086a61681", 554 | "sha256:f78a8df21a79bcc30cfd400bdc38f314333de7c0fb619763f6b9dabab8268bb7" 555 | ], 556 | "markers": "python_version < '3.9'", 557 | "version": "==5.9.0" 558 | }, 559 | "iniconfig": { 560 | "hashes": [ 561 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 562 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 563 | ], 564 | "version": "==1.1.1" 565 | }, 566 | "ipykernel": { 567 | "hashes": [ 568 | "sha256:7fe42c0d58435e971dc15fd42189f20d66bf35f3056bda4f6554271bc1fa3d0d", 569 | "sha256:d3d95241cd4dd302fea9d5747b00509b58997356d1f6333c9a074c3eccb78cb3" 570 | ], 571 | "markers": "python_version >= '3.7'", 572 | "version": "==6.16.0" 573 | }, 574 | "ipython": { 575 | "hashes": [ 576 | "sha256:097bdf5cd87576fd066179c9f7f208004f7a6864ee1b20f37d346c0bcb099f84", 577 | "sha256:6f090e29ab8ef8643e521763a4f1f39dc3914db643122b1e9d3328ff2e43ada2" 578 | ], 579 | "markers": "python_version >= '3.8'", 580 | "version": "==8.5.0" 581 | }, 582 | "ipython-genutils": { 583 | "hashes": [ 584 | "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", 585 | "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" 586 | ], 587 | "version": "==0.2.0" 588 | }, 589 | "ipywidgets": { 590 | "hashes": [ 591 | "sha256:08cb75c6e0a96836147cbfdc55580ae04d13e05d26ffbc377b4e1c68baa28b1f", 592 | "sha256:1dc3dd4ee19ded045ea7c86eb273033d238d8e43f9e7872c52d092683f263891" 593 | ], 594 | "markers": "python_version >= '3.7'", 595 | "version": "==8.0.2" 596 | }, 597 | "jaraco.classes": { 598 | "hashes": [ 599 | "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158", 600 | "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a" 601 | ], 602 | "markers": "python_version >= '3.7'", 603 | "version": "==3.2.3" 604 | }, 605 | "jedi": { 606 | "hashes": [ 607 | "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d", 608 | "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab" 609 | ], 610 | "markers": "python_version >= '3.6'", 611 | "version": "==0.18.1" 612 | }, 613 | "jeepney": { 614 | "hashes": [ 615 | "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", 616 | "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755" 617 | ], 618 | "markers": "sys_platform == 'linux'", 619 | "version": "==0.8.0" 620 | }, 621 | "jinja2": { 622 | "hashes": [ 623 | "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", 624 | "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" 625 | ], 626 | "markers": "python_version >= '3.7'", 627 | "version": "==3.1.2" 628 | }, 629 | "jinja2-time": { 630 | "hashes": [ 631 | "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40", 632 | "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa" 633 | ], 634 | "version": "==0.2.0" 635 | }, 636 | "jsonschema": { 637 | "hashes": [ 638 | "sha256:165059f076eff6971bae5b742fc029a7b4ef3f9bcf04c14e4776a7605de14b23", 639 | "sha256:9e74b8f9738d6a946d70705dc692b74b5429cd0960d58e79ffecfc43b2221eb9" 640 | ], 641 | "markers": "python_version >= '3.7'", 642 | "version": "==4.16.0" 643 | }, 644 | "jupyter": { 645 | "hashes": [ 646 | "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", 647 | "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", 648 | "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f" 649 | ], 650 | "index": "pypi", 651 | "version": "==1.0.0" 652 | }, 653 | "jupyter-client": { 654 | "hashes": [ 655 | "sha256:3c58466a1b8d55dba0bf3ce0834e4f5b7760baf98d1d73db0add6f19de9ecd1d", 656 | "sha256:b33222bdc9dd1714228bd286af006533a0abe2bbc093e8f3d29dc0b91bdc2be4" 657 | ], 658 | "markers": "python_version >= '3.7'", 659 | "version": "==7.3.5" 660 | }, 661 | "jupyter-console": { 662 | "hashes": [ 663 | "sha256:172f5335e31d600df61613a97b7f0352f2c8250bbd1092ef2d658f77249f89fb", 664 | "sha256:756df7f4f60c986e7bc0172e4493d3830a7e6e75c08750bbe59c0a5403ad6dee" 665 | ], 666 | "markers": "python_version >= '3.7'", 667 | "version": "==6.4.4" 668 | }, 669 | "jupyter-core": { 670 | "hashes": [ 671 | "sha256:2e5f244d44894c4154d06aeae3419dd7f1b0ef4494dc5584929b398c61cfd314", 672 | "sha256:715e22bb6cc7db3718fddfac1f69f1c7e899ca00e42bdfd4bf3705452b9fd84a" 673 | ], 674 | "markers": "python_version >= '3.7'", 675 | "version": "==4.11.1" 676 | }, 677 | "jupyterlab-pygments": { 678 | "hashes": [ 679 | "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f", 680 | "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d" 681 | ], 682 | "markers": "python_version >= '3.7'", 683 | "version": "==0.2.2" 684 | }, 685 | "jupyterlab-widgets": { 686 | "hashes": [ 687 | "sha256:6aa1bc0045470d54d76b9c0b7609a8f8f0087573bae25700a370c11f82cb38c8", 688 | "sha256:c767181399b4ca8b647befe2d913b1260f51bf9d8ef9b7a14632d4c1a7b536bd" 689 | ], 690 | "markers": "python_version >= '3.7'", 691 | "version": "==3.0.3" 692 | }, 693 | "keyring": { 694 | "hashes": [ 695 | "sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0", 696 | "sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5" 697 | ], 698 | "markers": "python_version >= '3.7'", 699 | "version": "==23.9.3" 700 | }, 701 | "kiwisolver": { 702 | "hashes": [ 703 | "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b", 704 | "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166", 705 | "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c", 706 | "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c", 707 | "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0", 708 | "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4", 709 | "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9", 710 | "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286", 711 | "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767", 712 | "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c", 713 | "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6", 714 | "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b", 715 | "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004", 716 | "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf", 717 | "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494", 718 | "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac", 719 | "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626", 720 | "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766", 721 | "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514", 722 | "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6", 723 | "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f", 724 | "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d", 725 | "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191", 726 | "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d", 727 | "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51", 728 | "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f", 729 | "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8", 730 | "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454", 731 | "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb", 732 | "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da", 733 | "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8", 734 | "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de", 735 | "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a", 736 | "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9", 737 | "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008", 738 | "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3", 739 | "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32", 740 | "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938", 741 | "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1", 742 | "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9", 743 | "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d", 744 | "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824", 745 | "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b", 746 | "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd", 747 | "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2", 748 | "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5", 749 | "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69", 750 | "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3", 751 | "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae", 752 | "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597", 753 | "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e", 754 | "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955", 755 | "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca", 756 | "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a", 757 | "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea", 758 | "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede", 759 | "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4", 760 | "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6", 761 | "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686", 762 | "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408", 763 | "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871", 764 | "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29", 765 | "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750", 766 | "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897", 767 | "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0", 768 | "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2", 769 | "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09", 770 | "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c" 771 | ], 772 | "markers": "python_version >= '3.7'", 773 | "version": "==1.4.4" 774 | }, 775 | "livereload": { 776 | "hashes": [ 777 | "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869" 778 | ], 779 | "version": "==2.6.3" 780 | }, 781 | "markupsafe": { 782 | "hashes": [ 783 | "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", 784 | "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", 785 | "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", 786 | "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", 787 | "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", 788 | "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", 789 | "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", 790 | "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", 791 | "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", 792 | "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", 793 | "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", 794 | "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", 795 | "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", 796 | "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", 797 | "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", 798 | "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", 799 | "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", 800 | "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", 801 | "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", 802 | "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", 803 | "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", 804 | "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", 805 | "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", 806 | "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", 807 | "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", 808 | "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", 809 | "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", 810 | "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", 811 | "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", 812 | "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", 813 | "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", 814 | "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", 815 | "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", 816 | "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", 817 | "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", 818 | "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", 819 | "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", 820 | "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", 821 | "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", 822 | "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" 823 | ], 824 | "markers": "python_version >= '3.7'", 825 | "version": "==2.1.1" 826 | }, 827 | "matplotlib": { 828 | "hashes": [ 829 | "sha256:0958fc3fdc59c1b716ee1a5d14e73d03d541d873241a37c5c3a86f7ef6017923", 830 | "sha256:0ae1b9b555212c1e242666af80e7ed796705869581e2d749971db4e682ccc1f3", 831 | "sha256:11c1987b803cc2b26725659cfe817478f0a9597878e5c4bf374cfe4e12cbbd79", 832 | "sha256:140316427a7c384e3dd37efb3a73cd67e14b0b237a6d277def91227f43cdcec2", 833 | "sha256:1559213b803959a2b8309122585b5226d1c2fb66c933b1a2094cf1e99cb4fb90", 834 | "sha256:16a899b958dd76606b571bc7eaa38f09160c27dfb262e493584644cfd4a77f0f", 835 | "sha256:1739935d293d0348d7bf662e8cd0edb9c2aa8f20ccd646db755ce0f3456d24e4", 836 | "sha256:1a4835c177821f3729be27ae9be7b8ae209fe75e83db7d9b2bfd319a998f0a42", 837 | "sha256:2b60d4abcb6a405ca7d909c80791b00637d22c62aa3bb0ffff7e589f763867f5", 838 | "sha256:2ed779a896b70c8012fe301fb91ee37e713e1dda1eb8f37de04cdbf506706983", 839 | "sha256:3ec2edf7f74829eae287aa53d64d83ad5d43ee51d29fb1d88e689d8b36028312", 840 | "sha256:408bbf968c15e9e38df9f25a588e372e28a43240cf5884c9bc6039a5021b7d5b", 841 | "sha256:4699bb671dbc4afdb544eb893e4deb8a34e294b7734733f65b4fd2787ba5fbc6", 842 | "sha256:4eba6972b796d97c8fcc5266b6dc42ef27c2dce4421b846cded0f3af851b81c9", 843 | "sha256:51092d13499be72e47c15c3a1ae0209edaca6be42b65ffbbefbe0c85f6153c6f", 844 | "sha256:62319d57dab5ad3e3494dd97a214e22079d3f72a0c8a2fd001829c2c6abbf8d1", 845 | "sha256:657fb7712185f82211170ac4debae0800ed4f5992b8f7ebba2a9eabaf133a857", 846 | "sha256:66a0db13f77aa7806dba29273874cf862450c61c2e5158245d17ee85d983fe8e", 847 | "sha256:6b98e098549d3aea2bfb93f38f0b2ecadcb423fa1504bbff902c01efdd833fd8", 848 | "sha256:7127e2b94571318531caf098dc9e8f60f5aba1704600f0b2483bf151d535674a", 849 | "sha256:798559837156b8e2e2df97cffca748c5c1432af6ec5004c2932e475d813f1743", 850 | "sha256:802feae98addb9f21707649a7f229c90a59fad34511881f20b906a5e8e6ea475", 851 | "sha256:89e1978c3fbe4e3d4c6ad7db7e6f982607cb2546f982ccbe42708392437b1972", 852 | "sha256:9295ca10a140c21e40d2ee43ef423213dc20767f6cea6b87c36973564bc51095", 853 | "sha256:9711ef291e184b5a73c9d3af3f2d5cfe25d571c8dd95aa498415f74ac7e221a8", 854 | "sha256:b0320f882214f6ffde5992081520b57b55450510bdaa020e96aacff9b7ae10e6", 855 | "sha256:b5bd3b3ff191f81509d9a1afd62e1e3cda7a7889c35b5b6359a1241fe1511015", 856 | "sha256:baa19508d8445f5648cd1ffe4fc6d4f7daf8b876f804e9a453df6c3708f6200b", 857 | "sha256:c5108ebe67da60a9204497d8d403316228deb52b550388190c53a57394d41531", 858 | "sha256:ccea337fb9a44866c5300c594b13d4d87e827ebc3c353bff15d298bac976b654", 859 | "sha256:cd73a16a759865831be5a8fb6546f2a908c8d7d7f55c75f94ee7c2ca13cc95de", 860 | "sha256:d840712f4b4c7d2a119f993d7e43ca9bcaa73aeaa24c322fa2bdf4f689a3ee09", 861 | "sha256:df26a09d955b3ab9b6bc18658b9403ed839096c97d7abe8806194e228a485a3c", 862 | "sha256:e01382c06ac3710155a0ca923047c5abe03c676d08f03e146c6a240d0a910713", 863 | "sha256:e572c67958f7d55eae77f5f64dc7bd31968cc9f24c233926833efe63c60545f2", 864 | "sha256:eca6f59cd0729edaeaa7032d582dffce518a420d4961ef3e8c93dce86be352c3", 865 | "sha256:efd2e12f8964f8fb4ba1984df71d85d02ef0531e687e59f78ec8fc07271a3857", 866 | "sha256:efe9e8037b989b14bb1887089ae763385431cc06fe488406413079cfd2a3a089", 867 | "sha256:f0d5b9b14ccc7f539143ac9eb1c6b57d26d69ca52d30c3d719a7bc4123579e44", 868 | "sha256:f1954d71cdf15c19e7f3bf2235a4fe1600ba42f34d472c9495bcf54d75a43e4e", 869 | "sha256:fbbceb0a0dfe9213f6314510665a32ef25fe29b50657567cd00115fbfcb3b20d" 870 | ], 871 | "index": "pypi", 872 | "version": "==3.6.0" 873 | }, 874 | "matplotlib-inline": { 875 | "hashes": [ 876 | "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", 877 | "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" 878 | ], 879 | "markers": "python_version >= '3.5'", 880 | "version": "==0.1.6" 881 | }, 882 | "mistune": { 883 | "hashes": [ 884 | "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d", 885 | "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808" 886 | ], 887 | "version": "==2.0.4" 888 | }, 889 | "more-itertools": { 890 | "hashes": [ 891 | "sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2", 892 | "sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750" 893 | ], 894 | "markers": "python_version >= '3.5'", 895 | "version": "==8.14.0" 896 | }, 897 | "mypy": { 898 | "hashes": [ 899 | "sha256:1021c241e8b6e1ca5a47e4d52601274ac078a89845cfde66c6d5f769819ffa1d", 900 | "sha256:14d53cdd4cf93765aa747a7399f0961a365bcddf7855d9cef6306fa41de01c24", 901 | "sha256:175f292f649a3af7082fe36620369ffc4661a71005aa9f8297ea473df5772046", 902 | "sha256:26ae64555d480ad4b32a267d10cab7aec92ff44de35a7cd95b2b7cb8e64ebe3e", 903 | "sha256:41fd1cf9bc0e1c19b9af13a6580ccb66c381a5ee2cf63ee5ebab747a4badeba3", 904 | "sha256:5085e6f442003fa915aeb0a46d4da58128da69325d8213b4b35cc7054090aed5", 905 | "sha256:58f27ebafe726a8e5ccb58d896451dd9a662a511a3188ff6a8a6a919142ecc20", 906 | "sha256:6389af3e204975d6658de4fb8ac16f58c14e1bacc6142fee86d1b5b26aa52bda", 907 | "sha256:724d36be56444f569c20a629d1d4ee0cb0ad666078d59bb84f8f887952511ca1", 908 | "sha256:75838c649290d83a2b83a88288c1eb60fe7a05b36d46cbea9d22efc790002146", 909 | "sha256:7b35ce03a289480d6544aac85fa3674f493f323d80ea7226410ed065cd46f206", 910 | "sha256:85f7a343542dc8b1ed0a888cdd34dca56462654ef23aa673907305b260b3d746", 911 | "sha256:86ebe67adf4d021b28c3f547da6aa2cce660b57f0432617af2cca932d4d378a6", 912 | "sha256:8ee8c2472e96beb1045e9081de8e92f295b89ac10c4109afdf3a23ad6e644f3e", 913 | "sha256:91781eff1f3f2607519c8b0e8518aad8498af1419e8442d5d0afb108059881fc", 914 | "sha256:a692a8e7d07abe5f4b2dd32d731812a0175626a90a223d4b58f10f458747dd8a", 915 | "sha256:a705a93670c8b74769496280d2fe6cd59961506c64f329bb179970ff1d24f9f8", 916 | "sha256:c6e564f035d25c99fd2b863e13049744d96bd1947e3d3d2f16f5828864506763", 917 | "sha256:cebca7fd333f90b61b3ef7f217ff75ce2e287482206ef4a8b18f32b49927b1a2", 918 | "sha256:d6af646bd46f10d53834a8e8983e130e47d8ab2d4b7a97363e35b24e1d588947", 919 | "sha256:e7aeaa763c7ab86d5b66ff27f68493d672e44c8099af636d433a7f3fa5596d40", 920 | "sha256:eaa97b9ddd1dd9901a22a879491dbb951b5dec75c3b90032e2baa7336777363b", 921 | "sha256:eb7a068e503be3543c4bd329c994103874fa543c1727ba5288393c21d912d795", 922 | "sha256:f793e3dd95e166b66d50e7b63e69e58e88643d80a3dcc3bcd81368e0478b089c" 923 | ], 924 | "index": "pypi", 925 | "version": "==0.982" 926 | }, 927 | "mypy-extensions": { 928 | "hashes": [ 929 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 930 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 931 | ], 932 | "version": "==0.4.3" 933 | }, 934 | "nbclient": { 935 | "hashes": [ 936 | "sha256:434c91385cf3e53084185334d675a0d33c615108b391e260915d1aa8e86661b8", 937 | "sha256:a1d844efd6da9bc39d2209bf996dbd8e07bf0f36b796edfabaa8f8a9ab77c3aa" 938 | ], 939 | "markers": "python_version >= '3.7'", 940 | "version": "==0.7.0" 941 | }, 942 | "nbconvert": { 943 | "hashes": [ 944 | "sha256:1e180801205ad831b6e2480c5a03307dfb6327fa5b2f9b156d6fed45f9700686", 945 | "sha256:50a54366ab53da20e82668818b7b2f3f7b85c0bcd46ec8e18836f12b39180dfa" 946 | ], 947 | "markers": "python_version >= '3.7'", 948 | "version": "==7.2.1" 949 | }, 950 | "nbformat": { 951 | "hashes": [ 952 | "sha256:146b5b9969391387c2089256359f5da7c718b1d8a88ba814320273ea410e646e", 953 | "sha256:9c071f0f615c1b0f4f9bf6745ecfd3294fc02daf279a05c76004c901e9dc5893" 954 | ], 955 | "markers": "python_version >= '3.7'", 956 | "version": "==5.6.1" 957 | }, 958 | "nest-asyncio": { 959 | "hashes": [ 960 | "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8", 961 | "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290" 962 | ], 963 | "markers": "python_version >= '3.5'", 964 | "version": "==1.5.6" 965 | }, 966 | "notebook": { 967 | "hashes": [ 968 | "sha256:6268c9ec9048cff7a45405c990c29ac9ca40b0bc3ec29263d218c5e01f2b4e86", 969 | "sha256:8c07a3bb7640e371f8a609bdbb2366a1976c6a2589da8ef917f761a61e3ad8b1" 970 | ], 971 | "markers": "python_version >= '3.7'", 972 | "version": "==6.4.12" 973 | }, 974 | "numpy": { 975 | "hashes": [ 976 | "sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089", 977 | "sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164", 978 | "sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440", 979 | "sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18", 980 | "sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c", 981 | "sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d", 982 | "sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c", 983 | "sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd", 984 | "sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036", 985 | "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd", 986 | "sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c", 987 | "sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4", 988 | "sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f", 989 | "sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d", 990 | "sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584", 991 | "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8", 992 | "sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a", 993 | "sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460", 994 | "sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6", 995 | "sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411", 996 | "sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1", 997 | "sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee", 998 | "sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7", 999 | "sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14", 1000 | "sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6", 1001 | "sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e", 1002 | "sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85", 1003 | "sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54" 1004 | ], 1005 | "markers": "python_version < '3.10'", 1006 | "version": "==1.23.3" 1007 | }, 1008 | "packaging": { 1009 | "hashes": [ 1010 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 1011 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 1012 | ], 1013 | "markers": "python_version >= '3.6'", 1014 | "version": "==21.3" 1015 | }, 1016 | "pandas": { 1017 | "hashes": [ 1018 | "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484", 1019 | "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc", 1020 | "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8", 1021 | "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b", 1022 | "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989", 1023 | "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8", 1024 | "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488", 1025 | "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f", 1026 | "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977", 1027 | "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f", 1028 | "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761", 1029 | "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c", 1030 | "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38", 1031 | "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515", 1032 | "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a", 1033 | "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a", 1034 | "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec", 1035 | "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3", 1036 | "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac", 1037 | "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b", 1038 | "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be", 1039 | "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d", 1040 | "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9", 1041 | "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8", 1042 | "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060", 1043 | "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009", 1044 | "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9" 1045 | ], 1046 | "index": "pypi", 1047 | "version": "==1.5.0" 1048 | }, 1049 | "pandocfilters": { 1050 | "hashes": [ 1051 | "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38", 1052 | "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f" 1053 | ], 1054 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1055 | "version": "==1.5.0" 1056 | }, 1057 | "parso": { 1058 | "hashes": [ 1059 | "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", 1060 | "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" 1061 | ], 1062 | "markers": "python_version >= '3.6'", 1063 | "version": "==0.8.3" 1064 | }, 1065 | "pexpect": { 1066 | "hashes": [ 1067 | "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", 1068 | "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" 1069 | ], 1070 | "markers": "sys_platform != 'win32'", 1071 | "version": "==4.8.0" 1072 | }, 1073 | "pickleshare": { 1074 | "hashes": [ 1075 | "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", 1076 | "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" 1077 | ], 1078 | "version": "==0.7.5" 1079 | }, 1080 | "pillow": { 1081 | "hashes": [ 1082 | "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", 1083 | "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", 1084 | "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", 1085 | "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", 1086 | "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", 1087 | "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", 1088 | "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", 1089 | "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", 1090 | "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", 1091 | "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", 1092 | "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", 1093 | "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", 1094 | "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", 1095 | "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", 1096 | "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", 1097 | "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437", 1098 | "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", 1099 | "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", 1100 | "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", 1101 | "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", 1102 | "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", 1103 | "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", 1104 | "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", 1105 | "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", 1106 | "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", 1107 | "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", 1108 | "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", 1109 | "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", 1110 | "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", 1111 | "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", 1112 | "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", 1113 | "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", 1114 | "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", 1115 | "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", 1116 | "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", 1117 | "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", 1118 | "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", 1119 | "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", 1120 | "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", 1121 | "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", 1122 | "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", 1123 | "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", 1124 | "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc", 1125 | "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", 1126 | "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", 1127 | "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", 1128 | "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", 1129 | "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", 1130 | "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", 1131 | "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", 1132 | "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", 1133 | "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", 1134 | "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", 1135 | "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", 1136 | "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", 1137 | "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", 1138 | "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", 1139 | "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" 1140 | ], 1141 | "markers": "python_version >= '3.7'", 1142 | "version": "==9.2.0" 1143 | }, 1144 | "pkginfo": { 1145 | "hashes": [ 1146 | "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594", 1147 | "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c" 1148 | ], 1149 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 1150 | "version": "==1.8.3" 1151 | }, 1152 | "pkgutil-resolve-name": { 1153 | "hashes": [ 1154 | "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174", 1155 | "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e" 1156 | ], 1157 | "markers": "python_version < '3.9'", 1158 | "version": "==1.3.10" 1159 | }, 1160 | "pluggy": { 1161 | "hashes": [ 1162 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 1163 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 1164 | ], 1165 | "markers": "python_version >= '3.6'", 1166 | "version": "==1.0.0" 1167 | }, 1168 | "prometheus-client": { 1169 | "hashes": [ 1170 | "sha256:522fded625282822a89e2773452f42df14b5a8e84a86433e3f8a189c1d54dc01", 1171 | "sha256:5459c427624961076277fdc6dc50540e2bacb98eebde99886e59ec55ed92093a" 1172 | ], 1173 | "markers": "python_version >= '3.6'", 1174 | "version": "==0.14.1" 1175 | }, 1176 | "prompt-toolkit": { 1177 | "hashes": [ 1178 | "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d", 1179 | "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148" 1180 | ], 1181 | "markers": "python_full_version >= '3.6.2'", 1182 | "version": "==3.0.31" 1183 | }, 1184 | "psutil": { 1185 | "hashes": [ 1186 | "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", 1187 | "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", 1188 | "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", 1189 | "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", 1190 | "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", 1191 | "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", 1192 | "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", 1193 | "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", 1194 | "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", 1195 | "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", 1196 | "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", 1197 | "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", 1198 | "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", 1199 | "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", 1200 | "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", 1201 | "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", 1202 | "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", 1203 | "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", 1204 | "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", 1205 | "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", 1206 | "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", 1207 | "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", 1208 | "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", 1209 | "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", 1210 | "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", 1211 | "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", 1212 | "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", 1213 | "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", 1214 | "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", 1215 | "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", 1216 | "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", 1217 | "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" 1218 | ], 1219 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1220 | "version": "==5.9.2" 1221 | }, 1222 | "ptyprocess": { 1223 | "hashes": [ 1224 | "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", 1225 | "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" 1226 | ], 1227 | "markers": "os_name != 'nt'", 1228 | "version": "==0.7.0" 1229 | }, 1230 | "pure-eval": { 1231 | "hashes": [ 1232 | "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", 1233 | "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" 1234 | ], 1235 | "version": "==0.2.2" 1236 | }, 1237 | "py": { 1238 | "hashes": [ 1239 | "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", 1240 | "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" 1241 | ], 1242 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1243 | "version": "==1.11.0" 1244 | }, 1245 | "pycparser": { 1246 | "hashes": [ 1247 | "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", 1248 | "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" 1249 | ], 1250 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1251 | "version": "==2.21" 1252 | }, 1253 | "pygments": { 1254 | "hashes": [ 1255 | "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", 1256 | "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" 1257 | ], 1258 | "markers": "python_version >= '3.6'", 1259 | "version": "==2.13.0" 1260 | }, 1261 | "pypandoc": { 1262 | "hashes": [ 1263 | "sha256:99013db9317fc631ba44bacdd61aed13cdabb77bfae3ee53de4684382c55c886", 1264 | "sha256:dbd6208ff1ff923072f53307926e8c40c71a9c8ba84e1b665e6cd011bebd4c07" 1265 | ], 1266 | "index": "pypi", 1267 | "version": "==1.9" 1268 | }, 1269 | "pyparsing": { 1270 | "hashes": [ 1271 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 1272 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 1273 | ], 1274 | "markers": "python_full_version >= '3.6.8'", 1275 | "version": "==3.0.9" 1276 | }, 1277 | "pyrsistent": { 1278 | "hashes": [ 1279 | "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c", 1280 | "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc", 1281 | "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e", 1282 | "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26", 1283 | "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec", 1284 | "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286", 1285 | "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045", 1286 | "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec", 1287 | "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8", 1288 | "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c", 1289 | "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca", 1290 | "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22", 1291 | "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a", 1292 | "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96", 1293 | "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc", 1294 | "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1", 1295 | "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07", 1296 | "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6", 1297 | "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b", 1298 | "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5", 1299 | "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6" 1300 | ], 1301 | "markers": "python_version >= '3.7'", 1302 | "version": "==0.18.1" 1303 | }, 1304 | "pytest": { 1305 | "hashes": [ 1306 | "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", 1307 | "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" 1308 | ], 1309 | "index": "pypi", 1310 | "version": "==7.1.3" 1311 | }, 1312 | "pytest-cov": { 1313 | "hashes": [ 1314 | "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b", 1315 | "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470" 1316 | ], 1317 | "index": "pypi", 1318 | "version": "==4.0.0" 1319 | }, 1320 | "python-dateutil": { 1321 | "hashes": [ 1322 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 1323 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 1324 | ], 1325 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1326 | "version": "==2.8.2" 1327 | }, 1328 | "python-slugify": { 1329 | "hashes": [ 1330 | "sha256:272d106cb31ab99b3496ba085e3fea0e9e76dcde967b5e9992500d1f785ce4e1", 1331 | "sha256:7b2c274c308b62f4269a9ba701aa69a797e9bca41aeee5b3a9e79e36b6656927" 1332 | ], 1333 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 1334 | "version": "==6.1.2" 1335 | }, 1336 | "pytz": { 1337 | "hashes": [ 1338 | "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91", 1339 | "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174" 1340 | ], 1341 | "version": "==2022.4" 1342 | }, 1343 | "pyyaml": { 1344 | "hashes": [ 1345 | "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", 1346 | "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", 1347 | "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", 1348 | "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", 1349 | "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", 1350 | "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", 1351 | "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", 1352 | "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", 1353 | "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", 1354 | "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", 1355 | "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", 1356 | "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", 1357 | "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", 1358 | "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", 1359 | "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", 1360 | "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", 1361 | "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", 1362 | "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", 1363 | "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", 1364 | "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", 1365 | "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", 1366 | "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", 1367 | "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", 1368 | "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", 1369 | "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", 1370 | "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", 1371 | "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", 1372 | "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", 1373 | "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", 1374 | "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", 1375 | "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", 1376 | "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", 1377 | "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", 1378 | "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", 1379 | "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", 1380 | "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", 1381 | "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", 1382 | "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", 1383 | "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", 1384 | "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" 1385 | ], 1386 | "markers": "python_version >= '3.6'", 1387 | "version": "==6.0" 1388 | }, 1389 | "pyzmq": { 1390 | "hashes": [ 1391 | "sha256:0108358dab8c6b27ff6b985c2af4b12665c1bc659648284153ee501000f5c107", 1392 | "sha256:07bec1a1b22dacf718f2c0e71b49600bb6a31a88f06527dfd0b5aababe3fa3f7", 1393 | "sha256:0e8f482c44ccb5884bf3f638f29bea0f8dc68c97e38b2061769c4cb697f6140d", 1394 | "sha256:0ec91f1bad66f3ee8c6deb65fa1fe418e8ad803efedd69c35f3b5502f43bd1dc", 1395 | "sha256:0f14cffd32e9c4c73da66db97853a6aeceaac34acdc0fae9e5bbc9370281864c", 1396 | "sha256:15975747462ec49fdc863af906bab87c43b2491403ab37a6d88410635786b0f4", 1397 | "sha256:1724117bae69e091309ffb8255412c4651d3f6355560d9af312d547f6c5bc8b8", 1398 | "sha256:1a7c280185c4da99e0cc06c63bdf91f5b0b71deb70d8717f0ab870a43e376db8", 1399 | "sha256:1b7928bb7580736ffac5baf814097be342ba08d3cfdfb48e52773ec959572287", 1400 | "sha256:2032d9cb994ce3b4cba2b8dfae08c7e25bc14ba484c770d4d3be33c27de8c45b", 1401 | "sha256:20e7eeb1166087db636c06cae04a1ef59298627f56fb17da10528ab52a14c87f", 1402 | "sha256:216f5d7dbb67166759e59b0479bca82b8acf9bed6015b526b8eb10143fb08e77", 1403 | "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066", 1404 | "sha256:3104f4b084ad5d9c0cb87445cc8cfd96bba710bef4a66c2674910127044df209", 1405 | "sha256:3e6192dbcefaaa52ed81be88525a54a445f4b4fe2fffcae7fe40ebb58bd06bfd", 1406 | "sha256:42d4f97b9795a7aafa152a36fe2ad44549b83a743fd3e77011136def512e6c2a", 1407 | "sha256:44e706bac34e9f50779cb8c39f10b53a4d15aebb97235643d3112ac20bd577b4", 1408 | "sha256:47b11a729d61a47df56346283a4a800fa379ae6a85870d5a2e1e4956c828eedc", 1409 | "sha256:4854f9edc5208f63f0841c0c667260ae8d6846cfa233c479e29fdc85d42ebd58", 1410 | "sha256:48f721f070726cd2a6e44f3c33f8ee4b24188e4b816e6dd8ba542c8c3bb5b246", 1411 | "sha256:52afb0ac962963fff30cf1be775bc51ae083ef4c1e354266ab20e5382057dd62", 1412 | "sha256:54d8b9c5e288362ec8595c1d98666d36f2070fd0c2f76e2b3c60fbad9bd76227", 1413 | "sha256:5bd3d7dfd9cd058eb68d9a905dec854f86649f64d4ddf21f3ec289341386c44b", 1414 | "sha256:613010b5d17906c4367609e6f52e9a2595e35d5cc27d36ff3f1b6fa6e954d944", 1415 | "sha256:624321120f7e60336be8ec74a172ae7fba5c3ed5bf787cc85f7e9986c9e0ebc2", 1416 | "sha256:65c94410b5a8355cfcf12fd600a313efee46ce96a09e911ea92cf2acf6708804", 1417 | "sha256:6640f83df0ae4ae1104d4c62b77e9ef39be85ebe53f636388707d532bee2b7b8", 1418 | "sha256:687700f8371643916a1d2c61f3fdaa630407dd205c38afff936545d7b7466066", 1419 | "sha256:77c2713faf25a953c69cf0f723d1b7dd83827b0834e6c41e3fb3bbc6765914a1", 1420 | "sha256:78068e8678ca023594e4a0ab558905c1033b2d3e806a0ad9e3094e231e115a33", 1421 | "sha256:7a23ccc1083c260fa9685c93e3b170baba45aeed4b524deb3f426b0c40c11639", 1422 | "sha256:7abddb2bd5489d30ffeb4b93a428130886c171b4d355ccd226e83254fcb6b9ef", 1423 | "sha256:80093b595921eed1a2cead546a683b9e2ae7f4a4592bb2ab22f70d30174f003a", 1424 | "sha256:8242543c522d84d033fe79be04cb559b80d7eb98ad81b137ff7e0a9020f00ace", 1425 | "sha256:838812c65ed5f7c2bd11f7b098d2e5d01685a3f6d1f82849423b570bae698c00", 1426 | "sha256:83ea1a398f192957cb986d9206ce229efe0ee75e3c6635baff53ddf39bd718d5", 1427 | "sha256:8421aa8c9b45ea608c205db9e1c0c855c7e54d0e9c2c2f337ce024f6843cab3b", 1428 | "sha256:858375573c9225cc8e5b49bfac846a77b696b8d5e815711b8d4ba3141e6e8879", 1429 | "sha256:86de64468cad9c6d269f32a6390e210ca5ada568c7a55de8e681ca3b897bb340", 1430 | "sha256:87f7ac99b15270db8d53f28c3c7b968612993a90a5cf359da354efe96f5372b4", 1431 | "sha256:8bad8210ad4df68c44ff3685cca3cda448ee46e20d13edcff8909eba6ec01ca4", 1432 | "sha256:8bb4af15f305056e95ca1bd086239b9ebc6ad55e9f49076d27d80027f72752f6", 1433 | "sha256:8c78bfe20d4c890cb5580a3b9290f700c570e167d4cdcc55feec07030297a5e3", 1434 | "sha256:8f3f3154fde2b1ff3aa7b4f9326347ebc89c8ef425ca1db8f665175e6d3bd42f", 1435 | "sha256:94010bd61bc168c103a5b3b0f56ed3b616688192db7cd5b1d626e49f28ff51b3", 1436 | "sha256:941fab0073f0a54dc33d1a0460cb04e0d85893cb0c5e1476c785000f8b359409", 1437 | "sha256:9dca7c3956b03b7663fac4d150f5e6d4f6f38b2462c1e9afd83bcf7019f17913", 1438 | "sha256:a180dbd5ea5d47c2d3b716d5c19cc3fb162d1c8db93b21a1295d69585bfddac1", 1439 | "sha256:a2712aee7b3834ace51738c15d9ee152cc5a98dc7d57dd93300461b792ab7b43", 1440 | "sha256:a435ef8a3bd95c8a2d316d6e0ff70d0db524f6037411652803e118871d703333", 1441 | "sha256:abb756147314430bee5d10919b8493c0ccb109ddb7f5dfd2fcd7441266a25b75", 1442 | "sha256:abe6eb10122f0d746a0d510c2039ae8edb27bc9af29f6d1b05a66cc2401353ff", 1443 | "sha256:acbd0a6d61cc954b9f535daaa9ec26b0a60a0d4353c5f7c1438ebc88a359a47e", 1444 | "sha256:ae08ac90aa8fa14caafc7a6251bd218bf6dac518b7bff09caaa5e781119ba3f2", 1445 | "sha256:ae61446166983c663cee42c852ed63899e43e484abf080089f771df4b9d272ef", 1446 | "sha256:afe1f3bc486d0ce40abb0a0c9adb39aed3bbac36ebdc596487b0cceba55c21c1", 1447 | "sha256:b946da90dc2799bcafa682692c1d2139b2a96ec3c24fa9fc6f5b0da782675330", 1448 | "sha256:b947e264f0e77d30dcbccbb00f49f900b204b922eb0c3a9f0afd61aaa1cedc3d", 1449 | "sha256:bb5635c851eef3a7a54becde6da99485eecf7d068bd885ac8e6d173c4ecd68b0", 1450 | "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6", 1451 | "sha256:c31805d2c8ade9b11feca4674eee2b9cce1fec3e8ddb7bbdd961a09dc76a80ea", 1452 | "sha256:c8840f064b1fb377cffd3efeaad2b190c14d4c8da02316dae07571252d20b31f", 1453 | "sha256:ccb94342d13e3bf3ffa6e62f95b5e3f0bc6bfa94558cb37f4b3d09d6feb536ff", 1454 | "sha256:d66689e840e75221b0b290b0befa86f059fb35e1ee6443bce51516d4d61b6b99", 1455 | "sha256:dabf1a05318d95b1537fd61d9330ef4313ea1216eea128a17615038859da3b3b", 1456 | "sha256:db03704b3506455d86ec72c3358a779e9b1d07b61220dfb43702b7b668edcd0d", 1457 | "sha256:de4217b9eb8b541cf2b7fde4401ce9d9a411cc0af85d410f9d6f4333f43640be", 1458 | "sha256:df0841f94928f8af9c7a1f0aaaffba1fb74607af023a152f59379c01c53aee58", 1459 | "sha256:dfb992dbcd88d8254471760879d48fb20836d91baa90f181c957122f9592b3dc", 1460 | "sha256:e7e66b4e403c2836ac74f26c4b65d8ac0ca1eef41dfcac2d013b7482befaad83", 1461 | "sha256:e8012bce6836d3f20a6c9599f81dfa945f433dab4dbd0c4917a6fb1f998ab33d", 1462 | "sha256:f01de4ec083daebf210531e2cca3bdb1608dbbbe00a9723e261d92087a1f6ebc", 1463 | "sha256:f0d945a85b70da97ae86113faf9f1b9294efe66bd4a5d6f82f2676d567338b66", 1464 | "sha256:fa0ae3275ef706c0309556061185dd0e4c4cd3b7d6f67ae617e4e677c7a41e2e" 1465 | ], 1466 | "markers": "python_version >= '3.6'", 1467 | "version": "==24.0.1" 1468 | }, 1469 | "qtconsole": { 1470 | "hashes": [ 1471 | "sha256:8eadf012e83ab018295803c247c6ab7eacd3d5ab1e1d88a0f37fdcfdab9295a3", 1472 | "sha256:c29d24464f57cdbaa17d6f6060be6e6d5e29126e7feb57eebc1747433382b3d1" 1473 | ], 1474 | "markers": "python_version >= '3.7'", 1475 | "version": "==5.3.2" 1476 | }, 1477 | "qtpy": { 1478 | "hashes": [ 1479 | "sha256:268cf5328f41353be1b127e04a81bc74ec9a9b54c9ac75dd8fe0ff48d8ad6ead", 1480 | "sha256:7d5231133b772e40b4ee514b6673aca558331e4b88ca038b26c9e16c5c95524f" 1481 | ], 1482 | "markers": "python_version >= '3.7'", 1483 | "version": "==2.2.1" 1484 | }, 1485 | "readme-renderer": { 1486 | "hashes": [ 1487 | "sha256:d3f06a69e8c40fca9ab3174eca48f96d9771eddb43517b17d96583418427b106", 1488 | "sha256:e8ad25293c98f781dbc2c5a36a309929390009f902f99e1798c761aaf04a7923" 1489 | ], 1490 | "markers": "python_version >= '3.7'", 1491 | "version": "==37.2" 1492 | }, 1493 | "requests": { 1494 | "hashes": [ 1495 | "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", 1496 | "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" 1497 | ], 1498 | "markers": "python_version >= '3.7' and python_version < '4'", 1499 | "version": "==2.28.1" 1500 | }, 1501 | "requests-toolbelt": { 1502 | "hashes": [ 1503 | "sha256:64c6b8c51b515d123f9f708a29743f44eb70c4479440641ed2df8c4dea56d985", 1504 | "sha256:f695d6207931200b46c8ef6addbc8a921fb5d77cc4cd209c2e7d39293fcd2b30" 1505 | ], 1506 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1507 | "version": "==0.10.0" 1508 | }, 1509 | "rfc3986": { 1510 | "hashes": [ 1511 | "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", 1512 | "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" 1513 | ], 1514 | "markers": "python_version >= '3.7'", 1515 | "version": "==2.0.0" 1516 | }, 1517 | "rich": { 1518 | "hashes": [ 1519 | "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e", 1520 | "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0" 1521 | ], 1522 | "markers": "python_version < '4' and python_full_version >= '3.6.3'", 1523 | "version": "==12.6.0" 1524 | }, 1525 | "secretstorage": { 1526 | "hashes": [ 1527 | "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", 1528 | "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99" 1529 | ], 1530 | "markers": "sys_platform == 'linux'", 1531 | "version": "==3.3.3" 1532 | }, 1533 | "send2trash": { 1534 | "hashes": [ 1535 | "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d", 1536 | "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08" 1537 | ], 1538 | "version": "==1.8.0" 1539 | }, 1540 | "six": { 1541 | "hashes": [ 1542 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 1543 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 1544 | ], 1545 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1546 | "version": "==1.16.0" 1547 | }, 1548 | "smmap": { 1549 | "hashes": [ 1550 | "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", 1551 | "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" 1552 | ], 1553 | "markers": "python_version >= '3.6'", 1554 | "version": "==5.0.0" 1555 | }, 1556 | "snowballstemmer": { 1557 | "hashes": [ 1558 | "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", 1559 | "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" 1560 | ], 1561 | "version": "==2.2.0" 1562 | }, 1563 | "soupsieve": { 1564 | "hashes": [ 1565 | "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", 1566 | "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" 1567 | ], 1568 | "markers": "python_version >= '3.6'", 1569 | "version": "==2.3.2.post1" 1570 | }, 1571 | "sphinx": { 1572 | "hashes": [ 1573 | "sha256:5b10cb1022dac8c035f75767799c39217a05fc0fe2d6fe5597560d38e44f0363", 1574 | "sha256:7abf6fabd7b58d0727b7317d5e2650ef68765bbe0ccb63c8795fa8683477eaa2" 1575 | ], 1576 | "index": "pypi", 1577 | "version": "==5.2.3" 1578 | }, 1579 | "sphinx-autobuild": { 1580 | "hashes": [ 1581 | "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac", 1582 | "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05" 1583 | ], 1584 | "index": "pypi", 1585 | "version": "==2021.3.14" 1586 | }, 1587 | "sphinx-autodoc-typehints": { 1588 | "hashes": [ 1589 | "sha256:e190d8ee8204c3de05a64f41cf10e592e987e4063c8ec0de7e4b11f6e036b2e2", 1590 | "sha256:ffd8e710f6757471b5c831c7ece88f52a9ff15f27836f4ef1c8695a64f8dcca8" 1591 | ], 1592 | "index": "pypi", 1593 | "version": "==1.19.4" 1594 | }, 1595 | "sphinx-copybutton": { 1596 | "hashes": [ 1597 | "sha256:9684dec7434bd73f0eea58dda93f9bb879d24bff2d8b187b1f2ec08dfe7b5f48", 1598 | "sha256:a0c059daadd03c27ba750da534a92a63e7a36a7736dcf684f26ee346199787f6" 1599 | ], 1600 | "index": "pypi", 1601 | "version": "==0.5.0" 1602 | }, 1603 | "sphinx-gallery": { 1604 | "hashes": [ 1605 | "sha256:56ccb29a0c2c4767d2a66617ba6ea7893e3a3885b6d972c62783a3b45b583ea5", 1606 | "sha256:b165cb366a5768a0f36e60e5bc6828fbf55fd1831e71645310167375223aa25c" 1607 | ], 1608 | "index": "pypi", 1609 | "version": "==0.11.1" 1610 | }, 1611 | "sphinx-paramlinks": { 1612 | "hashes": [ 1613 | "sha256:9f7bffa9f4197aa60369058c0a13ca5e7aa938e84c0643e300d341bbe2f4958a" 1614 | ], 1615 | "index": "pypi", 1616 | "version": "==0.5.4" 1617 | }, 1618 | "sphinx-rtd-theme": { 1619 | "editable": true, 1620 | "git": "https://github.com/readthedocs/sphinx_rtd_theme.git", 1621 | "ref": "ab7d388448258a24f8f4fa96dccb69d24f571736" 1622 | }, 1623 | "sphinx-sitemap": { 1624 | "hashes": [ 1625 | "sha256:65adda39233cb17c0da10ba1cebaa2df73e271cdb6f8efd5cec8eef3b3cf7737" 1626 | ], 1627 | "index": "pypi", 1628 | "version": "==2.2.0" 1629 | }, 1630 | "sphinxcontrib-applehelp": { 1631 | "hashes": [ 1632 | "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", 1633 | "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" 1634 | ], 1635 | "markers": "python_version >= '3.5'", 1636 | "version": "==1.0.2" 1637 | }, 1638 | "sphinxcontrib-devhelp": { 1639 | "hashes": [ 1640 | "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", 1641 | "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" 1642 | ], 1643 | "markers": "python_version >= '3.5'", 1644 | "version": "==1.0.2" 1645 | }, 1646 | "sphinxcontrib-fulltoc": { 1647 | "hashes": [ 1648 | "sha256:c845d62fc467f3135d4543e9f10e13ef91852683bd1c90fd19d07f9d36757cd9" 1649 | ], 1650 | "index": "pypi", 1651 | "version": "==1.2.0" 1652 | }, 1653 | "sphinxcontrib-htmlhelp": { 1654 | "hashes": [ 1655 | "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", 1656 | "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" 1657 | ], 1658 | "markers": "python_version >= '3.6'", 1659 | "version": "==2.0.0" 1660 | }, 1661 | "sphinxcontrib-jsmath": { 1662 | "hashes": [ 1663 | "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", 1664 | "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" 1665 | ], 1666 | "markers": "python_version >= '3.5'", 1667 | "version": "==1.0.1" 1668 | }, 1669 | "sphinxcontrib-qthelp": { 1670 | "hashes": [ 1671 | "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", 1672 | "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" 1673 | ], 1674 | "markers": "python_version >= '3.5'", 1675 | "version": "==1.0.3" 1676 | }, 1677 | "sphinxcontrib-serializinghtml": { 1678 | "hashes": [ 1679 | "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", 1680 | "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" 1681 | ], 1682 | "markers": "python_version >= '3.5'", 1683 | "version": "==1.1.5" 1684 | }, 1685 | "stack-data": { 1686 | "hashes": [ 1687 | "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2", 1688 | "sha256:95eb784942e861a3d80efd549ff9af6cf847d88343a12eb681d7157cfcb6e32b" 1689 | ], 1690 | "version": "==0.5.1" 1691 | }, 1692 | "terminado": { 1693 | "hashes": [ 1694 | "sha256:3e995072a7178a104c41134548ce9b03e4e7f0a538e9c29df4f1fbc81c7cfc75", 1695 | "sha256:fac14374eb5498bdc157ed32e510b1f60d5c3c7981a9f5ba018bb9a64cec0c25" 1696 | ], 1697 | "markers": "python_version >= '3.7'", 1698 | "version": "==0.16.0" 1699 | }, 1700 | "text-unidecode": { 1701 | "hashes": [ 1702 | "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", 1703 | "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" 1704 | ], 1705 | "version": "==1.3" 1706 | }, 1707 | "tinycss2": { 1708 | "hashes": [ 1709 | "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf", 1710 | "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8" 1711 | ], 1712 | "markers": "python_version >= '3.6'", 1713 | "version": "==1.1.1" 1714 | }, 1715 | "tomli": { 1716 | "hashes": [ 1717 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 1718 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 1719 | ], 1720 | "markers": "python_version >= '3.7'", 1721 | "version": "==2.0.1" 1722 | }, 1723 | "tornado": { 1724 | "hashes": [ 1725 | "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca", 1726 | "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72", 1727 | "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23", 1728 | "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8", 1729 | "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b", 1730 | "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9", 1731 | "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13", 1732 | "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75", 1733 | "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac", 1734 | "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e", 1735 | "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b" 1736 | ], 1737 | "markers": "python_version >= '3.7'", 1738 | "version": "==6.2" 1739 | }, 1740 | "tqdm": { 1741 | "hashes": [ 1742 | "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", 1743 | "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" 1744 | ], 1745 | "index": "pypi", 1746 | "version": "==4.64.1" 1747 | }, 1748 | "traitlets": { 1749 | "hashes": [ 1750 | "sha256:3f2c4e435e271592fe4390f1746ea56836e3a080f84e7833f0f801d9613fec39", 1751 | "sha256:93663cc8236093d48150e2af5e2ed30fc7904a11a6195e21bab0408af4e6d6c8" 1752 | ], 1753 | "markers": "python_version >= '3.7'", 1754 | "version": "==5.4.0" 1755 | }, 1756 | "twine": { 1757 | "hashes": [ 1758 | "sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e", 1759 | "sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0" 1760 | ], 1761 | "index": "pypi", 1762 | "version": "==4.0.1" 1763 | }, 1764 | "typer": { 1765 | "hashes": [ 1766 | "sha256:2d5720a5e63f73eaf31edaa15f6ab87f35f0690f8ca233017d7d23d743a91d73", 1767 | "sha256:54b19e5df18654070a82f8c2aa1da456a4ac16a2a83e6dcd9f170e291c56338e" 1768 | ], 1769 | "markers": "python_version >= '3.6'", 1770 | "version": "==0.6.1" 1771 | }, 1772 | "typing-extensions": { 1773 | "hashes": [ 1774 | "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", 1775 | "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" 1776 | ], 1777 | "markers": "python_version >= '3.7'", 1778 | "version": "==4.4.0" 1779 | }, 1780 | "urllib3": { 1781 | "hashes": [ 1782 | "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", 1783 | "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" 1784 | ], 1785 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", 1786 | "version": "==1.26.12" 1787 | }, 1788 | "wcwidth": { 1789 | "hashes": [ 1790 | "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", 1791 | "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" 1792 | ], 1793 | "version": "==0.2.5" 1794 | }, 1795 | "webencodings": { 1796 | "hashes": [ 1797 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 1798 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 1799 | ], 1800 | "version": "==0.5.1" 1801 | }, 1802 | "widgetsnbextension": { 1803 | "hashes": [ 1804 | "sha256:34824864c062b0b3030ad78210db5ae6a3960dfb61d5b27562d6631774de0286", 1805 | "sha256:7f3b0de8fda692d31ef03743b598620e31c2668b835edbd3962d080ccecf31eb" 1806 | ], 1807 | "markers": "python_version >= '3.7'", 1808 | "version": "==4.0.3" 1809 | }, 1810 | "zipp": { 1811 | "hashes": [ 1812 | "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2", 1813 | "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" 1814 | ], 1815 | "markers": "python_version >= '3.7'", 1816 | "version": "==3.8.1" 1817 | } 1818 | }, 1819 | "develop": {} 1820 | } 1821 | --------------------------------------------------------------------------------