diff --git a/.github/workflows/.nox-session.yml b/.github/workflows/.hatch-run.yml similarity index 82% rename from .github/workflows/.nox-session.yml rename to .github/workflows/.hatch-run.yml index 827fb4192..b312869e4 100644 --- a/.github/workflows/.nox-session.yml +++ b/.github/workflows/.hatch-run.yml @@ -1,4 +1,4 @@ -name: Nox Session +name: hatch-run on: workflow_call: @@ -6,12 +6,9 @@ on: job-name: required: true type: string - nox-args: + hatch-run: required: true type: string - nox-session-args: - required: false - type: string runs-on-array: required: false type: string @@ -20,6 +17,10 @@ on: required: false type: string default: '["3.x"]' + node-registry-url: + required: false + type: string + default: "" secrets: node-auth-token: required: false @@ -29,19 +30,19 @@ on: required: false jobs: - nox-session: + hatch: name: ${{ format(inputs.job-name, matrix.python-version, matrix.runs-on) }} strategy: matrix: - runs-on: ${{ fromJson(inputs.runs-on-array) }} python-version: ${{ fromJson(inputs.python-version-array) }} + runs-on: ${{ fromJson(inputs.runs-on-array) }} runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: "14.x" - registry-url: "https://registry.npmjs.org" + registry-url: ${{ inputs.node-registry-url }} - name: Pin NPM Version run: npm install -g npm@8.19.3 - name: Use Python ${{ matrix.python-version }} @@ -49,10 +50,10 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python Dependencies - run: pip install -r requirements/nox-deps.txt - - name: Run Sessions + run: pip install hatch poetry + - name: Run Scripts env: NODE_AUTH_TOKEN: ${{ secrets.node-auth-token }} PYPI_USERNAME: ${{ secrets.pypi-username }} PYPI_PASSWORD: ${{ secrets.pypi-password }} - run: nox ${{ inputs.nox-args }} --stop-on-first-error -- ${{ inputs.nox-session-args }} + run: hatch run ${{ inputs.hatch-run }} diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 000000000..af768579c --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,45 @@ +name: check + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: "0 0 * * 0" + +jobs: + test-py-cov: + uses: ./.github/workflows/.hatch-run.yml + with: + job-name: "python-{0}" + hatch-run: "test-py" + lint-py: + uses: ./.github/workflows/.hatch-run.yml + with: + job-name: "python-{0}" + hatch-run: "lint-py" + test-py-matrix: + uses: ./.github/workflows/.hatch-run.yml + with: + job-name: "python-{0} {1}" + hatch-run: "test-py --no-cov" + runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]' + python-version-array: '["3.9", "3.10", "3.11"]' + test-docs: + uses: ./.github/workflows/.hatch-run.yml + with: + job-name: "python-{0}" + hatch-run: "test-docs" + test-js: + uses: ./.github/workflows/.hatch-run.yml + with: + job-name: "{1}" + hatch-run: "test-js" + lint-js: + uses: ./.github/workflows/.hatch-run.yml + with: + job-name: "{1}" + hatch-run: "lint-js" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 18090e4b0..e9271cbd5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,10 +9,11 @@ on: jobs: publish: - uses: ./.github/workflows/.nox-session.yml + uses: ./.github/workflows/.hatch-run.yml with: job-name: "publish" - nox-args: "-s publish" + hatch-run: "publish" + node-registry-url: "https://registry.npmjs.org" secrets: node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }} pypi-username: ${{ secrets.PYPI_USERNAME }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 0b733f924..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: test - -on: - push: - branches: - - main - pull_request: - branches: - - main - schedule: - - cron: "0 0 * * 0" - -jobs: - python-exhaustive: - uses: ./.github/workflows/.nox-session.yml - with: - job-name: "python-{0}" - nox-args: "-t check-python" - nox-session-args: "--pytest --maxfail=3 --reruns 3" - python-environments: - uses: ./.github/workflows/.nox-session.yml - with: - job-name: "python-{0} {1}" - nox-args: "-s check-python-tests" - nox-session-args: "--no-cov --pytest --maxfail=3 --reruns 3" - runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]' - python-version-array: '["3.7", "3.8", "3.9", "3.10", "3.11"]' - docs: - uses: ./.github/workflows/.nox-session.yml - with: - job-name: "python-{0}" - nox-args: "-s check-docs" - javascript: - uses: ./.github/workflows/.nox-session.yml - with: - job-name: "{1}" - nox-args: "-t check-javascript" diff --git a/.gitignore b/.gitignore index 652e5015b..20c041e11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# --- Build Artifacts --- -src/reactpy/_client - # --- Jupyter --- *.ipynb_checkpoints *Untitled*.ipynb diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index b28e5d978..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,14 +0,0 @@ -repos: - - repo: https://github.com/ambv/black - rev: 23.1.0 - hooks: - - id: black - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.6 - hooks: - - id: prettier diff --git a/docs/Dockerfile b/docs/Dockerfile index 12e133d2c..ee28b3265 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -8,42 +8,26 @@ RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - RUN apt-get install -yq nodejs build-essential RUN npm install -g npm@8.5.0 -# Create Python Venv -# ------------------ -ENV VIRTUAL_ENV=/opt/venv -RUN python3 -m venv $VIRTUAL_ENV -ENV PATH="$VIRTUAL_ENV/bin:$PATH" -RUN pip install --upgrade pip - -# Install ReactPy +# Install Pipx # ------------ -COPY requirements ./requirements -RUN pip install -r requirements/build-docs.txt +RUN pip install pipx +# Copy Files +# ---------- +COPY LICENSE ./ COPY src ./src -COPY scripts ./scripts -COPY setup.py ./ -COPY pyproject.toml ./ -COPY MANIFEST.in ./ -COPY README.md ./ -RUN pip install .[all] - -# COPY License -# ----------- -COPY LICENSE /app/ - -# Build the Docs -# -------------- -COPY docs/__init__.py ./docs/ -COPY docs/app.py ./docs/ -COPY docs/examples.py ./docs/ -COPY docs/source ./docs/source +COPY docs ./docs COPY branding ./branding -RUN sphinx-build -v -W -b html docs/source docs/build + +# Install and Build Docs +# ---------------------- +WORKDIR /app/docs +RUN pipx run poetry install +RUN pipx run poetry run sphinx-build -v -W -b html source build # Define Entrypoint # ----------------- ENV PORT 5000 ENV REACTPY_DEBUG_MODE=1 ENV REACTPY_CHECK_VDOM_SPEC=0 -CMD python scripts/run_docs.py +CMD pipx run poetry run python main.py diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 69fe55ecf..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/__init__.py b/docs/docs_app/__init__.py similarity index 100% rename from docs/__init__.py rename to docs/docs_app/__init__.py diff --git a/docs/app.py b/docs/docs_app/app.py similarity index 77% rename from docs/app.py rename to docs/docs_app/app.py index 0b1ad302a..3fe4669ff 100644 --- a/docs/app.py +++ b/docs/docs_app/app.py @@ -3,14 +3,15 @@ from sanic import Sanic, response +from docs_app.examples import get_normalized_example_name, load_examples from reactpy import component from reactpy.backend.sanic import Options, configure, use_request from reactpy.core.types import ComponentConstructor -from .examples import get_normalized_example_name, load_examples +THIS_DIR = Path(__file__).parent +DOCS_DIR = THIS_DIR.parent +DOCS_BUILD_DIR = DOCS_DIR / "build" - -HERE = Path(__file__).parent REACTPY_MODEL_SERVER_URL_PREFIX = "/_reactpy" logger = getLogger(__name__) @@ -40,13 +41,13 @@ def reload_examples(): _EXAMPLES: dict[str, ComponentConstructor] = {} -def make_app(): - app = Sanic("docs_app") +def make_app(name: str): + app = Sanic(name) - app.static("/docs", str(HERE / "build")) + app.static("/docs", str(DOCS_BUILD_DIR)) @app.route("/") - async def forward_to_index(request): + async def forward_to_index(_): return response.redirect("/docs/index.html") configure( diff --git a/scripts/live_docs.py b/docs/docs_app/dev.py similarity index 88% rename from scripts/live_docs.py rename to docs/docs_app/dev.py index 3f7d8b207..5d661924d 100644 --- a/scripts/live_docs.py +++ b/docs/docs_app/dev.py @@ -13,22 +13,19 @@ get_parser, ) -from docs.app import make_app, reload_examples +from docs_app.app import make_app, reload_examples from reactpy.backend.sanic import serve_development_app from reactpy.testing import clear_reactpy_web_modules_dir - # these environment variable are used in custom Sphinx extensions os.environ["REACTPY_DOC_EXAMPLE_SERVER_HOST"] = "127.0.0.1:5555" os.environ["REACTPY_DOC_STATIC_SERVER_HOST"] = "" -_running_reactpy_servers = [] - def wrap_builder(old_builder): # This is the bit that we're injecting to get the example components to reload too - app = make_app() + app = make_app("docs_dev_app") thread_started = threading.Event() @@ -87,8 +84,8 @@ def main(): ignore_handler = _get_ignore_handler(args) server.watch(srcdir, builder, ignore=ignore_handler) for dirpath in args.additional_watched_dirs: - dirpath = os.path.realpath(dirpath) - server.watch(dirpath, builder, ignore=ignore_handler) + real_dirpath = os.path.realpath(dirpath) + server.watch(real_dirpath, builder, ignore=ignore_handler) server.watch(outdir, ignore=ignore_handler) if not args.no_initial_build: @@ -100,12 +97,8 @@ def main(): def opener(): time.sleep(args.delay) - webbrowser.open("http://%s:%s/index.html" % (args.host, args.port)) + webbrowser.open(f"http://{args.host}:{args.port}/index.html") threading.Thread(target=opener, daemon=True).start() server.serve(port=portn, host=args.host, root=outdir) - - -if __name__ == "__main__": - main() diff --git a/docs/examples.py b/docs/docs_app/examples.py similarity index 97% rename from docs/examples.py rename to docs/docs_app/examples.py index 142d49429..a71a0b111 100644 --- a/docs/examples.py +++ b/docs/docs_app/examples.py @@ -1,16 +1,16 @@ from __future__ import annotations +from collections.abc import Iterator from io import StringIO from pathlib import Path from traceback import format_exc -from typing import Callable, Iterator +from typing import Callable import reactpy from reactpy.types import ComponentType - HERE = Path(__file__) -SOURCE_DIR = HERE.parent / "source" +SOURCE_DIR = HERE.parent.parent / "source" CONF_FILE = SOURCE_DIR / "conf.py" RUN_ReactPy = reactpy.run @@ -148,7 +148,6 @@ def __init__(self, max_lines: int = 10): def set_callback(self, function: Callable[[str], None]) -> None: self._callback = function - return None def getvalue(self) -> str: return "".join(self._lines) diff --git a/docs/docs_app/prod.py b/docs/docs_app/prod.py new file mode 100644 index 000000000..0acf12432 --- /dev/null +++ b/docs/docs_app/prod.py @@ -0,0 +1,14 @@ +import os + +from docs_app.app import make_app + +app = make_app("docs_prod_app") + + +def main() -> None: + app.run( + host="0.0.0.0", # noqa: S104 + port=int(os.environ.get("PORT", 5000)), + workers=int(os.environ.get("WEB_CONCURRENCY", 1)), + debug=bool(int(os.environ.get("DEBUG", "0"))), + ) diff --git a/docs/main.py b/docs/main.py new file mode 100644 index 000000000..e3181f393 --- /dev/null +++ b/docs/main.py @@ -0,0 +1,9 @@ +import sys + +from docs_app import dev, prod + +if __name__ == "__main__": + if len(sys.argv) == 1: + prod.main() + else: + dev.main() diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 543c6b13b..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/poetry.lock b/docs/poetry.lock new file mode 100644 index 000000000..8e1daef24 --- /dev/null +++ b/docs/poetry.lock @@ -0,0 +1,2269 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiofiles" +version = "23.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"}, + {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"}, +] + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "anyio" +version = "3.7.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"}, + {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "asgiref" +version = "3.7.2" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "babel" +version = "2.12.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "certifi" +version = "2023.5.7" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "colorlog" +version = "6.7.0" +description = "Add colours to the output of Python's logging module." +optional = false +python-versions = ">=3.6" +files = [ + {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"}, + {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "contourpy" +version = "1.0.7" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8acf74b5d383414401926c1598ed77825cd530ac7b463ebc2e4f46638f56cce6"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c71fdd8f1c0f84ffd58fca37d00ca4ebaa9e502fb49825484da075ac0b0b803"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e9486bf1bb979d95d5cffed40689cb595abb2b841f2991fc894b3452290e8"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f4d8941a9564cda3f7fa6a6cd9b32ec575830780677932abdec7bcb61717b0"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e20e5a1908e18aaa60d9077a6d8753090e3f85ca25da6e25d30dc0a9e84c2c6"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a877ada905f7d69b2a31796c4b66e31a8068b37aa9b78832d41c82fc3e056ddd"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6381fa66866b0ea35e15d197fc06ac3840a9b2643a6475c8fff267db8b9f1e69"}, + {file = "contourpy-1.0.7-cp310-cp310-win32.whl", hash = "sha256:3c184ad2433635f216645fdf0493011a4667e8d46b34082f5a3de702b6ec42e3"}, + {file = "contourpy-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:3caea6365b13119626ee996711ab63e0c9d7496f65641f4459c60a009a1f3e80"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed33433fc3820263a6368e532f19ddb4c5990855e4886088ad84fd7c4e561c71"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38e2e577f0f092b8e6774459317c05a69935a1755ecfb621c0a98f0e3c09c9a5"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae90d5a8590e5310c32a7630b4b8618cef7563cebf649011da80874d0aa8f414"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130230b7e49825c98edf0b428b7aa1125503d91732735ef897786fe5452b1ec2"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58569c491e7f7e874f11519ef46737cea1d6eda1b514e4eb5ac7dab6aa864d02"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d43960d809c4c12508a60b66cb936e7ed57d51fb5e30b513934a4a23874fae"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:152fd8f730c31fd67fe0ffebe1df38ab6a669403da93df218801a893645c6ccc"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9056c5310eb1daa33fc234ef39ebfb8c8e2533f088bbf0bc7350f70a29bde1ac"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9d7587d2fdc820cc9177139b56795c39fb8560f540bba9ceea215f1f66e1566"}, + {file = "contourpy-1.0.7-cp311-cp311-win32.whl", hash = "sha256:4ee3ee247f795a69e53cd91d927146fb16c4e803c7ac86c84104940c7d2cabf0"}, + {file = "contourpy-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:5caeacc68642e5f19d707471890f037a13007feba8427eb7f2a60811a1fc1350"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd7dc0e6812b799a34f6d12fcb1000539098c249c8da54f3566c6a6461d0dbad"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0f9d350b639db6c2c233d92c7f213d94d2e444d8e8fc5ca44c9706cf72193772"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e96a08b62bb8de960d3a6afbc5ed8421bf1a2d9c85cc4ea73f4bc81b4910500f"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:031154ed61f7328ad7f97662e48660a150ef84ee1bc8876b6472af88bf5a9b98"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9ebb4425fc1b658e13bace354c48a933b842d53c458f02c86f371cecbedecc"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb8f6d08ca7998cf59eaf50c9d60717f29a1a0a09caa46460d33b2924839dbd"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6c180d89a28787e4b73b07e9b0e2dac7741261dbdca95f2b489c4f8f887dd810"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b8d587cc39057d0afd4166083d289bdeff221ac6d3ee5046aef2d480dc4b503c"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:769eef00437edf115e24d87f8926955f00f7704bede656ce605097584f9966dc"}, + {file = "contourpy-1.0.7-cp38-cp38-win32.whl", hash = "sha256:62398c80ef57589bdbe1eb8537127321c1abcfdf8c5f14f479dbbe27d0322e66"}, + {file = "contourpy-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:57119b0116e3f408acbdccf9eb6ef19d7fe7baf0d1e9aaa5381489bc1aa56556"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30676ca45084ee61e9c3da589042c24a57592e375d4b138bd84d8709893a1ba4"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e927b3868bd1e12acee7cc8f3747d815b4ab3e445a28d2e5373a7f4a6e76ba1"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:366a0cf0fc079af5204801786ad7a1c007714ee3909e364dbac1729f5b0849e5"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ba9bb365446a22411f0673abf6ee1fea3b2cf47b37533b970904880ceb72f3"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b0bf0c30d432278793d2141362ac853859e87de0a7dee24a1cea35231f0d50"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7281244c99fd7c6f27c1c6bfafba878517b0b62925a09b586d88ce750a016d2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6d0f9e1d39dbfb3977f9dd79f156c86eb03e57a7face96f199e02b18e58d32a"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f6979d20ee5693a1057ab53e043adffa1e7418d734c1532e2d9e915b08d8ec2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dd34c1ae752515318224cba7fc62b53130c45ac6a1040c8b7c1a223c46e8967"}, + {file = "contourpy-1.0.7-cp39-cp39-win32.whl", hash = "sha256:c5210e5d5117e9aec8c47d9156d1d3835570dd909a899171b9535cb4a3f32693"}, + {file = "contourpy-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:60835badb5ed5f4e194a6f21c09283dd6e007664a86101431bf870d9e86266c4"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce41676b3d0dd16dbcfabcc1dc46090aaf4688fd6e819ef343dbda5a57ef0161"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a011cf354107b47c58ea932d13b04d93c6d1d69b8b6dce885e642531f847566"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31a55dccc8426e71817e3fe09b37d6d48ae40aae4ecbc8c7ad59d6893569c436"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69f8ff4db108815addd900a74df665e135dbbd6547a8a69333a68e1f6e368ac2"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efe99298ba37e37787f6a2ea868265465410822f7bea163edcc1bd3903354ea9"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1e97b86f73715e8670ef45292d7cc033548266f07d54e2183ecb3c87598888f"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc331c13902d0f50845099434cd936d49d7a2ca76cb654b39691974cb1e4812d"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24847601071f740837aefb730e01bd169fbcaa610209779a78db7ebb6e6a7051"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abf298af1e7ad44eeb93501e40eb5a67abbf93b5d90e468d01fc0c4451971afa"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"}, + {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"}, +] + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "chromedriver", "selenium"] +docs = ["furo", "sphinx-copybutton"] +mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"] +test = ["Pillow", "matplotlib", "pytest"] +test-no-images = ["pytest"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] + +[[package]] +name = "docutils" +version = "0.17.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.96.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.96.0-py3-none-any.whl", hash = "sha256:b8e11fe81e81eab4e1504209917338e0b80f783878a42c2b99467e5e1019a1e9"}, + {file = "fastapi-0.96.0.tar.gz", hash = "sha256:71232d47c2787446991c81c41c249f8a16238d52d779c0e6b43927d3773dbe3c"}, +] + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = ">=0.27.0,<0.28.0" + +[package.extras] +all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.21.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer-cli (>=0.0.13,<0.0.14)", "typer[all] (>=0.6.1,<0.8.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.7)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.7.0.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] + +[[package]] +name = "fastjsonschema" +version = "2.17.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.17.1-py3-none-any.whl", hash = "sha256:4b90b252628ca695280924d863fe37234eebadc29c5360d322571233dc9746e0"}, + {file = "fastjsonschema-2.17.1.tar.gz", hash = "sha256:f4eeb8a77cef54861dbf7424ac8ce71306f12cbb086c45131bcba2c6a4f726e3"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "flask" +version = "2.1.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Flask-2.1.3-py3-none-any.whl", hash = "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c"}, + {file = "Flask-2.1.3.tar.gz", hash = "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb"}, +] + +[package.dependencies] +click = ">=8.0" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-cors" +version = "3.0.10" +description = "A Flask extension adding a decorator for CORS support" +optional = false +python-versions = "*" +files = [ + {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, + {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, +] + +[package.dependencies] +Flask = ">=0.9" +Six = "*" + +[[package]] +name = "flask-sock" +version = "0.6.0" +description = "WebSocket support for Flask" +optional = false +python-versions = ">=3.6" +files = [ + {file = "flask-sock-0.6.0.tar.gz", hash = "sha256:435cf81bb497ac7622cd1dda554fbfa3e369e629daea0a1d21b73a24f1bd6229"}, + {file = "flask_sock-0.6.0-py3-none-any.whl", hash = "sha256:593fffb186928080a5b5b03d717efc56dac2d5ed690ce6bfff333b3597a2f518"}, +] + +[package.dependencies] +flask = ">=2" +simple-websocket = ">=0.5.1" + +[[package]] +name = "fonttools" +version = "4.39.4" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.39.4-py3-none-any.whl", hash = "sha256:106caf6167c4597556b31a8d9175a3fdc0356fdcd70ab19973c3b0d4c893c461"}, + {file = "fonttools-4.39.4.zip", hash = "sha256:dba8d7cdb8e2bac1b3da28c5ed5960de09e59a2fe7e63bb73f5a59e57b0430d2"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "furo" +version = "2022.4.7" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.6" +files = [ + {file = "furo-2022.4.7-py3-none-any.whl", hash = "sha256:7f3e3d2fb977483590f8ecb2c2cd511bd82661b79c18efb24de9558bc9cdf2d7"}, + {file = "furo-2022.4.7.tar.gz", hash = "sha256:96204ab7cd047e4b6c523996e0279c4c629a8fc31f4f109b2efd470c17f49c80"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7,<3.0" +sphinx = ">=4.0,<5.0" + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "html5tagger" +version = "1.3.0" +description = "Pythonic HTML generation/templating (no template files)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "html5tagger-1.3.0-py3-none-any.whl", hash = "sha256:ce14313515edffec8ed8a36c5890d023922641171b4e6e5774ad1a74998f5351"}, + {file = "html5tagger-1.3.0.tar.gz", hash = "sha256:84fa3dfb49e5c83b79bbd856ab7b1de8e2311c3bb46a8be925f119e3880a8da9"}, +] + +[[package]] +name = "httptools" +version = "0.5.0" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.5.0" +files = [ + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, + {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, + {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, + {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, + {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, + {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, + {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, + {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, + {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, + {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.6.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "importlib-resources" +version = "5.12.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsonpatch" +version = "1.32" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"}, + {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.3" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "jsonpointer-2.3-py2.py3-none-any.whl", hash = "sha256:51801e558539b4e9cd268638c078c6c5746c9ac96bc38152d443400e4f3793e9"}, + {file = "jsonpointer-2.3.tar.gz", hash = "sha256:97cba51526c829282218feb99dab1b1e6bdf8efd1c43dc9d57be093c0d69c99a"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] + +[[package]] +name = "livereload" +version = "2.6.3" +description = "Python LiveReload is an awesome tool for web developers" +optional = false +python-versions = "*" +files = [ + {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, + {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, +] + +[package.dependencies] +six = "*" +tornado = {version = "*", markers = "python_version > \"2.7\""} + +[[package]] +name = "lxml" +version = "4.9.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"}, + {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"}, + {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"}, + {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, + {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, + {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"}, + {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"}, + {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, + {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, + {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, + {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, + {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, + {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, + {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"}, + {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"}, + {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"}, + {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"}, + {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"}, + {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"}, + {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"}, + {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"}, + {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"}, + {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.6" +files = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +] + +[[package]] +name = "matplotlib" +version = "3.7.1" +description = "Python plotting package" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"}, + {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"}, + {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"}, + {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"}, + {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"}, + {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"}, + {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"}, + {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"}, + {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"}, + {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.0.1" +numpy = ">=1.20" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.24.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, + {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, + {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, + {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, + {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, + {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, + {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, + {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, + {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, + {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pillow" +version = "9.5.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "playwright" +version = "1.34.0" +description = "A high-level API to automate web browsers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "playwright-1.34.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:69bb9b3296e366a23a99277b4c7673cb54ce71a3f5d630f114f7701b61f98f25"}, + {file = "playwright-1.34.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:402d946631c8458436e099d7731bbf54cf79c9e62e3acae0ea8421e72616926b"}, + {file = "playwright-1.34.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:462251cda0fcbb273497d357dbe14b11e43ebceb0bac9b892beda041ff209aa9"}, + {file = "playwright-1.34.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:a8ba124ea302596a03a66993cd500484fb255cbc10fe0757fa4d49f974267a80"}, + {file = "playwright-1.34.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf0cb6aac49d24335fe361868aea72b11f276a95e7809f1a5d1c69b4120c46ac"}, + {file = "playwright-1.34.0-py3-none-win32.whl", hash = "sha256:c50fef189d87243cc09ae0feb8e417fbe434359ccbcc863fb19ba06d46d31c33"}, + {file = "playwright-1.34.0-py3-none-win_amd64.whl", hash = "sha256:42e16c930e1e910461f4c551a72fc1b900f37124431bf2b6a6d9ddae70042db4"}, +] + +[package.dependencies] +greenlet = "2.0.2" +pyee = "9.0.4" + +[[package]] +name = "pydantic" +version = "1.10.8" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d"}, + {file = "pydantic-1.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f"}, + {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f"}, + {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319"}, + {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277"}, + {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab"}, + {file = "pydantic-1.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800"}, + {file = "pydantic-1.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33"}, + {file = "pydantic-1.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5"}, + {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85"}, + {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f"}, + {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e"}, + {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4"}, + {file = "pydantic-1.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd"}, + {file = "pydantic-1.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878"}, + {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4"}, + {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b"}, + {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68"}, + {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea"}, + {file = "pydantic-1.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c"}, + {file = "pydantic-1.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887"}, + {file = "pydantic-1.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6"}, + {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18"}, + {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375"}, + {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1"}, + {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108"}, + {file = "pydantic-1.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56"}, + {file = "pydantic-1.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e"}, + {file = "pydantic-1.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0"}, + {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459"}, + {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4"}, + {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1"}, + {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01"}, + {file = "pydantic-1.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a"}, + {file = "pydantic-1.10.8-py3-none-any.whl", hash = "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2"}, + {file = "pydantic-1.10.8.tar.gz", hash = "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyee" +version = "9.0.4" +description = "A port of node.js's EventEmitter to python." +optional = false +python-versions = "*" +files = [ + {file = "pyee-9.0.4-py2.py3-none-any.whl", hash = "sha256:9f066570130c554e9cc12de5a9d86f57c7ee47fece163bbdaa3e9c933cfbdfa5"}, + {file = "pyee-9.0.4.tar.gz", hash = "sha256:2770c4928abc721f46b705e6a72b0c59480c4a69c9a83ca0b00bb994f1ea4b32"}, +] + +[package.dependencies] +typing-extensions = "*" + +[[package]] +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "reactpy" +version = "1.0.0" +description = "Reactive user interfaces with pure Python" +optional = false +python-versions = ">=3.9" +files = [] +develop = false + +[package.dependencies] +anyio = ">=3" +asgiref = ">=3" +colorlog = ">=6" +fastapi = {version = ">=0.63.0", optional = true, markers = "extra == \"fastapi\""} +fastjsonschema = ">=2.14.5" +flask = {version = "*", optional = true, markers = "extra == \"flask\""} +flask-cors = {version = "*", optional = true, markers = "extra == \"flask\""} +flask-sock = {version = "*", optional = true, markers = "extra == \"flask\""} +jsonpatch = ">=1.32" +lxml = ">=4" +markupsafe = {version = ">=1.1.1,<2.1", optional = true, markers = "extra == \"flask\""} +mypy-extensions = ">=0.4.3" +playwright = {version = "*", optional = true, markers = "extra == \"testing\""} +requests = ">=2" +sanic = {version = ">=21", optional = true, markers = "extra == \"sanic\""} +sanic-cors = {version = "*", optional = true, markers = "extra == \"sanic\""} +starlette = {version = ">=0.13.6", optional = true, markers = "extra == \"starlette\""} +tornado = {version = "*", optional = true, markers = "extra == \"tornado\""} +typing-extensions = ">=3.10" +uvicorn = {version = ">=0.19.0", extras = ["standard"], optional = true, markers = "extra == \"fastapi\" or extra == \"sanic\" or extra == \"starlette\""} + +[package.extras] +all = ["reactpy[fastapi,flask,sanic,starlette,testing,tornado]"] +fastapi = ["fastapi (>=0.63.0)", "uvicorn[standard] (>=0.19.0)"] +flask = ["flask", "flask-cors", "flask-sock", "markupsafe (>=1.1.1,<2.1)"] +sanic = ["sanic (>=21)", "sanic-cors", "uvicorn[standard] (>=0.19.0)"] +starlette = ["starlette (>=0.13.6)", "uvicorn[standard] (>=0.19.0)"] +testing = ["playwright"] +tornado = ["tornado"] + +[package.source] +type = "directory" +url = "../src/py/reactpy" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "sanic" +version = "23.3.0" +description = "A web server and web framework that's written to go fast. Build fast. Run fast." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sanic-23.3.0-py3-none-any.whl", hash = "sha256:7cafbd63da9c6c6d8aeb8cb4304addf8a274352ab812014386c63e55f474fbee"}, + {file = "sanic-23.3.0.tar.gz", hash = "sha256:b80ebc5c38c983cb45ae5ecc7a669a54c823ec1dff297fbd5f817b1e9e9e49af"}, +] + +[package.dependencies] +aiofiles = ">=0.6.0" +html5tagger = ">=1.2.1" +httptools = ">=0.0.10" +multidict = ">=5.0,<7.0" +sanic-routing = ">=22.8.0" +tracerite = ">=1.0.0" +ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} +uvloop = {version = ">=0.15.0", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} +websockets = ">=10.0" + +[package.extras] +all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "flake8", "isort (>=5.0.0)", "m2r2", "mistune (<2.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] +docs = ["docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] +ext = ["sanic-ext"] +http3 = ["aioquic"] +test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn (<0.15.0)"] + +[[package]] +name = "sanic-cors" +version = "2.2.0" +description = "A Sanic extension adding a decorator for CORS support. Based on flask-cors by Cory Dolphin." +optional = false +python-versions = "*" +files = [ + {file = "Sanic-Cors-2.2.0.tar.gz", hash = "sha256:f8d7515da4c8b837871d422c66314c4b5704396a78894b59c50e26aa72a95873"}, + {file = "Sanic_Cors-2.2.0-py2.py3-none-any.whl", hash = "sha256:c3b133ff1f0bb609a53db35f727f5c371dc4ebeb6be4cc2c37c19dd8b9301115"}, +] + +[package.dependencies] +packaging = ">=21.3" +sanic = ">=21.9.3" + +[[package]] +name = "sanic-routing" +version = "22.8.0" +description = "Core routing component for Sanic" +optional = false +python-versions = "*" +files = [ + {file = "sanic-routing-22.8.0.tar.gz", hash = "sha256:305729b4e0bf01f074044a2a315ff401fa7eeffb009eec1d2c81d35e1038ddfc"}, + {file = "sanic_routing-22.8.0-py3-none-any.whl", hash = "sha256:9a928ed9e19a36bc019223be90a5da0ab88cdd76b101e032510b6a7073c017e9"}, +] + +[[package]] +name = "simple-websocket" +version = "0.10.0" +description = "Simple WebSocket server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "simple-websocket-0.10.0.tar.gz", hash = "sha256:82c0b0b1006d5490f09ff66392394d90dd758285635edad241e093e9a8abd3eb"}, + {file = "simple_websocket-0.10.0-py3-none-any.whl", hash = "sha256:fc1bc56c393a187e7268f8ab99da1a8e8da9b5dfb7769a2f3b8dada00067745b"}, +] + +[package.dependencies] +wsproto = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[[package]] +name = "sphinx" +version = "4.5.0" +description = "Python documentation generator" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=1.3" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.18" +imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.3" +packaging = "*" +Pygments = ">=2.0" +requests = ">=2.5.0" +snowballstemmer = ">=1.1" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] + +[[package]] +name = "sphinx-autobuild" +version = "2021.3.14" +description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, +] + +[package.dependencies] +colorama = "*" +livereload = "*" +sphinx = "*" + +[package.extras] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "1.19.1" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_autodoc_typehints-1.19.1-py3-none-any.whl", hash = "sha256:9be46aeeb1b315eb5df1f3a7cb262149895d16c7d7dcd77b92513c3c3a1e85e6"}, + {file = "sphinx_autodoc_typehints-1.19.1.tar.gz", hash = "sha256:6c841db55e0e9be0483ff3962a2152b60e79306f4288d8c4e7e86ac84486a5ea"}, +] + +[package.dependencies] +Sphinx = ">=4.5" + +[package.extras] +testing = ["covdefaults (>=2.2)", "coverage (>=6.3)", "diff-cover (>=6.4)", "nptyping (>=2.1.2)", "pytest (>=7.1)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=4.1)"] +type-comments = ["typed-ast (>=1.5.2)"] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinx-design" +version = "0.4.1" +description = "A sphinx extension for designing beautiful, view size responsive web components." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_design-0.4.1-py3-none-any.whl", hash = "sha256:23bf5705eb31296d4451f68b0222a698a8a84396ffe8378dfd9319ba7ab8efd9"}, + {file = "sphinx_design-0.4.1.tar.gz", hash = "sha256:5b6418ba4a2dc3d83592ea0ff61a52a891fe72195a4c3a18b2fa1c7668ce4708"}, +] + +[package.dependencies] +sphinx = ">=4,<7" + +[package.extras] +code-style = ["pre-commit (>=2.12,<3.0)"] +rtd = ["myst-parser (>=0.18.0,<2)"] +testing = ["myst-parser (>=0.18.0,<2)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"] +theme-furo = ["furo (>=2022.06.04,<2022.07)"] +theme-pydata = ["pydata-sphinx-theme (>=0.9.0,<0.10.0)"] +theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"] +theme-sbt = ["sphinx-book-theme (>=0.3.0,<0.4.0)"] + +[[package]] +name = "sphinx-reredirects" +version = "0.1.2" +description = "Handles redirects for moved pages in Sphinx documentation projects" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinx_reredirects-0.1.2-py3-none-any.whl", hash = "sha256:3a22161771aadd448bb608a4fe7277252182a337af53c18372b7104531d71489"}, + {file = "sphinx_reredirects-0.1.2.tar.gz", hash = "sha256:a0e7213304759b01edc22f032f1715a1c61176fc8f167164e7a52b9feec9ac64"}, +] + +[package.dependencies] +sphinx = "*" + +[[package]] +name = "sphinx-resolve-py-references" +version = "0.1.0" +description = "Better python object resolution in Sphinx" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_resolve_py_references-0.1.0-py2.py3-none-any.whl", hash = "sha256:ccf44a6b62d75c3a568285f4e1815734088c1a7cab7bbb7935bb22fbf0d78bc2"}, + {file = "sphinx_resolve_py_references-0.1.0.tar.gz", hash = "sha256:0f87c06b29ec128964aee2e40d170d1d3c0e5f4955b2618a89ca724f42385372"}, +] + +[package.dependencies] +sphinx = "*" + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxext-opengraph" +version = "0.8.2" +description = "Sphinx Extension to enable OGP support" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinxext-opengraph-0.8.2.tar.gz", hash = "sha256:45a693b6704052c426576f0a1f630649c55b4188bc49eb63e9587e24a923db39"}, + {file = "sphinxext_opengraph-0.8.2-py3-none-any.whl", hash = "sha256:6a05bdfe5176d9dd0a1d58a504f17118362ab976631213cd36fb44c4c40544c9"}, +] + +[package.dependencies] +matplotlib = "*" +sphinx = ">=4.0" + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "tornado" +version = "6.3.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"}, + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"}, + {file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"}, + {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"}, + {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"}, +] + +[[package]] +name = "tracerite" +version = "1.1.0" +description = "Human-readable HTML tracebacks for Python exceptions" +optional = false +python-versions = "*" +files = [ + {file = "tracerite-1.1.0-py3-none-any.whl", hash = "sha256:4cccac04db05eeeabda45e72b57199e147fa2f73cf64d89cfd625df321bd2ab6"}, + {file = "tracerite-1.1.0.tar.gz", hash = "sha256:041dab8fd4bb405f73506293ac7438a2d311e5f9044378ba7d9a6540392f9e4b"}, +] + +[package.dependencies] +html5tagger = ">=1.2.1" + +[[package]] +name = "typing-extensions" +version = "4.6.3" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, + {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, +] + +[[package]] +name = "ujson" +version = "5.7.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"}, + {file = "ujson-5.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aae4d9e1b4c7b61780f0a006c897a4a1904f862fdab1abb3ea8f45bd11aa58f3"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e43ccdba1cb5c6d3448eadf6fc0dae7be6c77e357a3abc968d1b44e265866d"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24ad1aa7fc4e4caa41d3d343512ce68e41411fb92adf7f434a4d4b3749dc8f58"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afff311e9f065a8f03c3753db7011bae7beb73a66189c7ea5fcb0456b7041ea4"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e80f0d03e7e8646fc3d79ed2d875cebd4c83846e129737fdc4c2532dbd43d9e"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:137831d8a0db302fb6828ee21c67ad63ac537bddc4376e1aab1c8573756ee21c"}, + {file = "ujson-5.7.0-cp310-cp310-win32.whl", hash = "sha256:7df3fd35ebc14dafeea031038a99232b32f53fa4c3ecddb8bed132a43eefb8ad"}, + {file = "ujson-5.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:af4639f684f425177d09ae409c07602c4096a6287027469157bfb6f83e01448b"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b0f2680ce8a70f77f5d70aaf3f013d53e6af6d7058727a35d8ceb4a71cdd4e9"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a19fd8e7d8cc58a169bea99fed5666023adf707a536d8f7b0a3c51dd498abf"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6abb8e6d8f1ae72f0ed18287245f5b6d40094e2656d1eab6d99d666361514074"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8cd622c069368d5074bd93817b31bdb02f8d818e57c29e206f10a1f9c6337dd"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14f9082669f90e18e64792b3fd0bf19f2b15e7fe467534a35ea4b53f3bf4b755"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7ff6ebb43bc81b057724e89550b13c9a30eda0f29c2f506f8b009895438f5a6"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7f241488879d91a136b299e0c4ce091996c684a53775e63bb442d1a8e9ae22a"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5593263a7fcfb934107444bcfba9dde8145b282de0ee9f61e285e59a916dda0f"}, + {file = "ujson-5.7.0-cp311-cp311-win32.whl", hash = "sha256:26c2b32b489c393106e9cb68d0a02e1a7b9d05a07429d875c46b94ee8405bdb7"}, + {file = "ujson-5.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed24406454bb5a31df18f0a423ae14beb27b28cdfa34f6268e7ebddf23da807e"}, + {file = "ujson-5.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18679484e3bf9926342b1c43a3bd640f93a9eeeba19ef3d21993af7b0c44785d"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee295761e1c6c30400641f0a20d381633d7622633cdf83a194f3c876a0e4b7e"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b738282e12a05f400b291966630a98d622da0938caa4bc93cf65adb5f4281c60"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00343501dbaa5172e78ef0e37f9ebd08040110e11c12420ff7c1f9f0332d939e"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c0d1f7c3908357ee100aa64c4d1cf91edf99c40ac0069422a4fd5fd23b263263"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a5d2f44331cf04689eafac7a6596c71d6657967c07ac700b0ae1c921178645da"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:16b2254a77b310f118717715259a196662baa6b1f63b1a642d12ab1ff998c3d7"}, + {file = "ujson-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:6faf46fa100b2b89e4db47206cf8a1ffb41542cdd34dde615b2fc2288954f194"}, + {file = "ujson-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ff0004c3f5a9a6574689a553d1b7819d1a496b4f005a7451f339dc2d9f4cf98c"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:75204a1dd7ec6158c8db85a2f14a68d2143503f4bafb9a00b63fe09d35762a5e"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7312731c7826e6c99cdd3ac503cd9acd300598e7a80bcf41f604fee5f49f566c"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b9dc5a90e2149643df7f23634fe202fed5ebc787a2a1be95cf23632b4d90651"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a6961fc48821d84b1198a09516e396d56551e910d489692126e90bf4887d29"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b01a9af52a0d5c46b2c68e3f258fdef2eacaa0ce6ae3e9eb97983f5b1166edb6"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7316d3edeba8a403686cdcad4af737b8415493101e7462a70ff73dd0609eafc"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ee997799a23227e2319a3f8817ce0b058923dbd31904761b788dc8f53bd3e30"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dda9aa4c33435147262cd2ea87c6b7a1ca83ba9b3933ff7df34e69fee9fced0c"}, + {file = "ujson-5.7.0-cp38-cp38-win32.whl", hash = "sha256:bea8d30e362180aafecabbdcbe0e1f0b32c9fa9e39c38e4af037b9d3ca36f50c"}, + {file = "ujson-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:c96e3b872bf883090ddf32cc41957edf819c5336ab0007d0cf3854e61841726d"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6411aea4c94a8e93c2baac096fbf697af35ba2b2ed410b8b360b3c0957a952d3"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d3b3499c55911f70d4e074c626acdb79a56f54262c3c83325ffb210fb03e44d"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341f891d45dd3814d31764626c55d7ab3fd21af61fbc99d070e9c10c1190680b"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f242eec917bafdc3f73a1021617db85f9958df80f267db69c76d766058f7b19"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3af9f9f22a67a8c9466a32115d9073c72a33ae627b11de6f592df0ee09b98b6"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a3d794afbf134df3056a813e5c8a935208cddeae975bd4bc0ef7e89c52f0ce0"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:800bf998e78dae655008dd10b22ca8dc93bdcfcc82f620d754a411592da4bbf2"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b5ac3d5c5825e30b438ea92845380e812a476d6c2a1872b76026f2e9d8060fc2"}, + {file = "ujson-5.7.0-cp39-cp39-win32.whl", hash = "sha256:cd90027e6d93e8982f7d0d23acf88c896d18deff1903dd96140613389b25c0dd"}, + {file = "ujson-5.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:523ee146cdb2122bbd827f4dcc2a8e66607b3f665186bce9e4f78c9710b6d8ab"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e87cec407ec004cf1b04c0ed7219a68c12860123dfb8902ef880d3d87a71c172"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab10165db6a7994e67001733f7f2caf3400b3e11538409d8756bc9b1c64f7e8"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b522be14a28e6ac1cf818599aeff1004a28b42df4ed4d7bc819887b9dac915fc"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7592f40175c723c032cdbe9fe5165b3b5903604f774ab0849363386e99e1f253"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ed22f9665327a981f288a4f758a432824dc0314e4195a0eaeb0da56a477da94d"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:adf445a49d9a97a5a4c9bb1d652a1528de09dd1c48b29f79f3d66cea9f826bf6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64772a53f3c4b6122ed930ae145184ebaed38534c60f3d859d8c3f00911eb122"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35209cb2c13fcb9d76d249286105b4897b75a5e7f0efb0c0f4b90f222ce48910"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90712dfc775b2c7a07d4d8e059dd58636bd6ff1776d79857776152e693bddea6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0e4e8981c6e7e9e637e637ad8ffe948a09e5434bc5f52ecbb82b4b4cfc092bfb"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581c945b811a3d67c27566539bfcb9705ea09cb27c4be0002f7a553c8886b817"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d36a807a24c7d44f71686685ae6fbc8793d784bca1adf4c89f5f780b835b6243"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4257307e3662aa65e2644a277ca68783c5d51190ed9c49efebdd3cbfd5fa44"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea7423d8a2f9e160c5e011119741682414c5b8dce4ae56590a966316a07a4618"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c592eb91a5968058a561d358d0fef59099ed152cfb3e1cd14eee51a7a93879e"}, + {file = "ujson-5.7.0.tar.gz", hash = "sha256:e788e5d5dcae8f6118ac9b45d0b891a0d55f7ac480eddcb7f07263f2bcf37b23"}, +] + +[[package]] +name = "urllib3" +version = "2.0.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, + {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.22.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, + {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.17.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, +] + +[package.extras] +dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "watchfiles" +version = "0.19.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"}, + {file = "watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"}, + {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"}, + {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"}, + {file = "watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"}, + {file = "watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"}, + {file = "watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"}, + {file = "watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + +[[package]] +name = "werkzeug" +version = "2.1.2" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"}, + {file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"}, +] + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "629118cfac10f1dab4c39c6ccd50bd69ca68a7fc05dd2baf1d020082d6b19e4e" diff --git a/docs/pyproject.toml b/docs/pyproject.toml new file mode 100644 index 000000000..d2f47c577 --- /dev/null +++ b/docs/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "docs" +version = "0.0.0" +description = "docs" +authors = ["rmorshea "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.9" +reactpy = { path = "../src/py/reactpy", extras = ["starlette", "sanic", "fastapi", "flask", "tornado", "testing"], develop = false } +furo = "2022.04.07" +sphinx = "*" +sphinx-autodoc-typehints = "*" +sphinx-copybutton = "*" +sphinx-autobuild = "*" +sphinx-reredirects = "*" +sphinx-design = "*" +sphinx-resolve-py-references = "*" +sphinxext-opengraph = "*" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json index fc00fcb83..98cbb7014 100644 --- a/docs/source/_custom_js/package-lock.json +++ b/docs/source/_custom_js/package-lock.json @@ -8,7 +8,7 @@ "name": "reactpy-docs-example-loader", "version": "1.0.0", "dependencies": { - "@reactpy/client": "file:../../../src/client/packages/@reactpy/client" + "@reactpy/client": "file:../../../src/js/packages/@reactpy/client" }, "devDependencies": { "@rollup/plugin-commonjs": "^21.0.1", @@ -21,6 +21,7 @@ "../../../src/client/packages/@reactpy/client": { "version": "0.3.1", "integrity": "sha512-pIK5eNwFSHKXg7ClpASWFVKyZDYxz59MSFpVaX/OqJFkrJaAxBuhKGXNTMXmuyWOL5Iyvb/ErwwDRxQRzMNkfQ==", + "extraneous": true, "license": "MIT", "dependencies": { "event-to-object": "^0.1.2", @@ -58,8 +59,26 @@ "react-dom": ">=16 <18" } }, + "../../../src/js/packages/@reactpy/client": { + "version": "0.3.1", + "license": "MIT", + "dependencies": { + "event-to-object": "^0.1.2", + "json-pointer": "^0.6.2" + }, + "devDependencies": { + "@types/json-pointer": "^1.0.31", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "typescript": "^4.9.5" + }, + "peerDependencies": { + "react": ">=16 <18", + "react-dom": ">=16 <18" + } + }, "node_modules/@reactpy/client": { - "resolved": "../../../src/client/packages/@reactpy/client", + "resolved": "../../../src/js/packages/@reactpy/client", "link": true }, "node_modules/@rollup/plugin-commonjs": { @@ -434,7 +453,7 @@ }, "dependencies": { "@reactpy/client": { - "version": "file:../../../src/client/packages/@reactpy/client", + "version": "file:../../../src/js/packages/@reactpy/client", "requires": { "@types/json-pointer": "^1.0.31", "@types/react": "^17.0", diff --git a/docs/source/_custom_js/package.json b/docs/source/_custom_js/package.json index 1d19613f5..78d72b961 100644 --- a/docs/source/_custom_js/package.json +++ b/docs/source/_custom_js/package.json @@ -15,6 +15,6 @@ "rollup": "^2.35.1" }, "dependencies": { - "@reactpy/client": "file:../../../src/client/packages/@reactpy/client" + "@reactpy/client": "file:../../../src/js/packages/@reactpy/client" } } diff --git a/docs/source/_exts/async_doctest.py b/docs/source/_exts/async_doctest.py index 905c1d00a..96024d488 100644 --- a/docs/source/_exts/async_doctest.py +++ b/docs/source/_exts/async_doctest.py @@ -6,7 +6,6 @@ from sphinx.ext.doctest import DocTestBuilder from sphinx.ext.doctest import setup as doctest_setup - test_template = """ import asyncio as __test_template_asyncio @@ -41,10 +40,8 @@ def test_runner(self) -> DocTestRunner: @test_runner.setter def test_runner(self, value: DocTestRunner) -> None: self._test_runner = TestRunnerWrapper(value) - return None def setup(app: Sphinx) -> None: doctest_setup(app) app.add_builder(AsyncDoctestBuilder, override=True) - return None diff --git a/docs/source/_exts/autogen_api_docs.py b/docs/source/_exts/autogen_api_docs.py index 9f833ac14..b95d85a99 100644 --- a/docs/source/_exts/autogen_api_docs.py +++ b/docs/source/_exts/autogen_api_docs.py @@ -1,15 +1,14 @@ from __future__ import annotations import sys +from collections.abc import Collection, Iterator from pathlib import Path -from typing import Collection, Iterator from sphinx.application import Sphinx - HERE = Path(__file__).parent SRC = HERE.parent.parent.parent / "src" -PYTHON_PACKAGE = SRC / "reactpy" +PYTHON_PACKAGE = SRC / "py" / "reactpy" / "reactpy" AUTO_DIR = HERE.parent / "_auto" AUTO_DIR.mkdir(exist_ok=True) @@ -82,9 +81,12 @@ def get_module_name(path: Path) -> str: def get_section_symbol(path: Path) -> str: - rel_path_parts = path.relative_to(PYTHON_PACKAGE).parts - assert len(rel_path_parts) < len(SECTION_SYMBOLS), "package structure is too deep" - return SECTION_SYMBOLS[len(rel_path_parts)] + rel_path = path.relative_to(PYTHON_PACKAGE) + rel_path_parts = rel_path.parts + if len(rel_path_parts) > len(SECTION_SYMBOLS): + msg = f"package structure is too deep - ran out of section symbols: {rel_path}" + raise RuntimeError(msg) + return SECTION_SYMBOLS[len(rel_path_parts) - 1] def walk_python_files(root: Path, ignore_dirs: Collection[str]) -> Iterator[Path]: diff --git a/docs/source/_exts/build_custom_js.py b/docs/source/_exts/build_custom_js.py index b84378353..97857ba74 100644 --- a/docs/source/_exts/build_custom_js.py +++ b/docs/source/_exts/build_custom_js.py @@ -3,11 +3,10 @@ from sphinx.application import Sphinx - SOURCE_DIR = Path(__file__).parent.parent CUSTOM_JS_DIR = SOURCE_DIR / "_custom_js" def setup(app: Sphinx) -> None: - subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True) - subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True) + subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True) # noqa S607 + subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True) # noqa S607 diff --git a/docs/source/_exts/custom_autosectionlabel.py b/docs/source/_exts/custom_autosectionlabel.py index 9610ad684..92ff5e2df 100644 --- a/docs/source/_exts/custom_autosectionlabel.py +++ b/docs/source/_exts/custom_autosectionlabel.py @@ -17,7 +17,6 @@ from sphinx.util import logging from sphinx.util.nodes import clean_astext - logger = logging.getLogger(__name__) diff --git a/docs/source/_exts/reactpy_example.py b/docs/source/_exts/reactpy_example.py index 7cad84853..c6b054c07 100644 --- a/docs/source/_exts/reactpy_example.py +++ b/docs/source/_exts/reactpy_example.py @@ -4,18 +4,17 @@ from pathlib import Path from typing import Any +from docs_app.examples import ( + SOURCE_DIR, + get_example_files_by_name, + get_normalized_example_name, +) from docutils.parsers.rst import directives from docutils.statemachine import StringList from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective from sphinx_design.tabs import TabSetDirective -from docs.examples import ( - SOURCE_DIR, - get_example_files_by_name, - get_normalized_example_name, -) - class WidgetExample(SphinxDirective): has_content = False @@ -41,10 +40,8 @@ def run(self): ex_files = get_example_files_by_name(example_name) if not ex_files: src_file, line_num = self.get_source_info() - raise ValueError( - f"Missing example named {example_name!r} " - f"referenced by document {src_file}:{line_num}" - ) + msg = f"Missing example named {example_name!r} referenced by document {src_file}:{line_num}" + raise ValueError(msg) labeled_tab_items: list[tuple[str, Any]] = [] if len(ex_files) == 1: @@ -114,7 +111,8 @@ def _literal_include(path: Path, linenos: bool): ".json": "json", }[path.suffix] except KeyError: - raise ValueError(f"Unknown extension type {path.suffix!r}") + msg = f"Unknown extension type {path.suffix!r}" + raise ValueError(msg) from None return _literal_include_template.format( name=str(path.relative_to(SOURCE_DIR)), diff --git a/docs/source/_exts/reactpy_view.py b/docs/source/_exts/reactpy_view.py index 477a6caca..7a2bf85a4 100644 --- a/docs/source/_exts/reactpy_view.py +++ b/docs/source/_exts/reactpy_view.py @@ -1,13 +1,14 @@ import os +import sys +print(sys.path) + +from docs_app.examples import get_normalized_example_name from docutils.nodes import raw from docutils.parsers.rst import directives from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective -from docs.examples import get_normalized_example_name - - _REACTPY_EXAMPLE_HOST = os.environ.get("REACTPY_DOC_EXAMPLE_SERVER_HOST", "") _REACTPY_STATIC_HOST = os.environ.get("REACTPY_DOC_STATIC_SERVER_HOST", "/docs").rstrip( "/" diff --git a/docs/source/about/contributor-guide.rst b/docs/source/about/contributor-guide.rst index e26d89e52..b44be9b7e 100644 --- a/docs/source/about/contributor-guide.rst +++ b/docs/source/about/contributor-guide.rst @@ -80,12 +80,18 @@ In order to develop ReactPy locally you'll first need to install the following: * - What to Install - How to Install + * - Python >= 3.9 + - https://realpython.com/installing-python/ + + * - Hatch + - https://hatch.pypa.io/latest/install/ + + * - Poetry + - https://python-poetry.org/docs/#installation + * - Git - https://git-scm.com/book/en/v2/Getting-Started-Installing-Git - * - Python >= 3.7 - - https://realpython.com/installing-python/ - * - NodeJS >= 14 - https://nodejs.org/en/download/package-manager/ @@ -106,57 +112,38 @@ Once done, you can clone a local copy of this repository: git clone https://github.com/reactive-python/reactpy.git cd reactpy -Then, you should be able to run the command below to: - -- Install an editable version of the Python code - -- Download, build, and install Javascript dependencies - -- Install some pre-commit_ hooks for Git +Then, you should be able to activate your development environment with: .. code-block:: bash - pip install -e . -r requirements.txt && pre-commit install - -If you modify any Javascript, you'll need to re-install ReactPy: - -.. code-block:: bash - - pip install -e . - -However you may also ``cd`` to the ``src/client`` directory which contains a -``package.json`` that you can use to run standard ``npm`` commands from. + hatch shell Running The Tests ----------------- -The test suite for ReactPy is executed with Nox_, which should already be installed if you -followed the `earlier instructions `_. The suite covers: - -1. Server-side Python code with PyTest_ - -2. The end-to-end application using Playwright_ in Python - -3. Client-side Javascript code with UVU_ - -Once you've installed them you'll be able to run: +Tests exist for both Python and Javascript. These can be run with the following: .. code-block:: bash - nox -s check-python-tests + hatch run test-py + hatch run test-js -You can observe the browser as the tests are running by passing an extra flag: +If you want to run tests for individual packages you'll need to ``cd`` into the +package directory and run the tests from there. For example, to run the tests just for +the ``reactpy`` package you'd do: .. code-block:: bash - nox -s check-python-tests -- --headed + cd src/py/reactpy + hatch run test --headed # run the tests in a browser window -To see a full list of available commands (e.g. ``nox -s ``) run: +For Javascript, you'd do: .. code-block:: bash - nox -l + cd src/js/packages/event-to-object + npm run check:tests Code Quality Checks @@ -172,8 +159,9 @@ The following are currently being used: - MyPy_ - a static type checker - Black_ - an opinionated code formatter - Flake8_ - a style guide enforcement tool -- ISort_ - a utility for alphabetically sorting imports +- Ruff_ - An extremely fast Python linter, written in Rust. - Prettier_ - a tool for automatically formatting various file types +- EsLint_ - A Javascript linter The most strict measure of quality enforced on the codebase is 100% test coverage in Python files. This means that every line of coded added to ReactPy requires a test case @@ -186,10 +174,10 @@ your :ref:`Pull Request `. .. note:: - You can manually run ``nox -s format`` to auto format your code without having to - do so via ``pre-commit``. However, many IDEs have ways to automatically format upon - saving a file - (e.g.`VSCode `__) + You can manually run ``hatch run lint --fix`` to auto format your code without + having to do so via ``pre-commit``. However, many IDEs have ways to automatically + format upon saving a file (e.g. + `VSCode `__) Building The Documentation @@ -199,7 +187,7 @@ To build and display the documentation locally run: .. code-block:: bash - nox -s docs + hatch run docs This will compile the documentation from its source files into HTML, start a web server, and open a browser to display the now generated documentation. Whenever you change any @@ -211,14 +199,14 @@ To run some of the examples in the documentation as if they were tests run: .. code-block:: bash - nox -s test_docs + hatch run test-docs Building the documentation as it's deployed in production requires Docker_. Once you've installed Docker, you can run: .. code-block:: bash - nox -s docs_in_docker + hatch run docs --docker Where you can then navigate to http://localhost:5000.. @@ -329,7 +317,6 @@ you should refer to their respective documentation in the links below: .. _pip: https://pypi.org/project/pip/ .. _PyTest: pytest Callable[[bool], None]: - ... - - -LanguageName: TypeAlias = "Literal['py', 'js']" - - -# --- Constants ------------------------------------------------------------------------ - - -ROOT_DIR = Path(__file__).parent.resolve() -SRC_DIR = ROOT_DIR / "src" -CLIENT_DIR = SRC_DIR / "client" -REACTPY_DIR = SRC_DIR / "reactpy" -TAG_PATTERN = re.compile( - # start - r"^" - # package name - r"(?P[0-9a-zA-Z-@/]+)-" - # package version - r"v(?P[0-9][0-9a-zA-Z-\.\+]*)" - # end - r"$" -) -REMAINING_ARGS = Option(nargs=REMAINDER, type=str) - - -# --- Session Setup -------------------------------------------------------------------- - - -group = NoxOpt(auto_tag=True) - - -@group.setup -def setup_checks(session: Session) -> None: - session.install("--upgrade", "pip") - session.run("pip", "--version") - session.run("npm", "--version", external=True) - - -@group.setup("check-javascript") -def setup_javascript_checks(session: Session) -> None: - session.chdir(CLIENT_DIR) - session.run("npm", "ci", external=True) - - -# --- Session Definitions -------------------------------------------------------------- - - -@group.session -def format(session: Session) -> None: - """Auto format Python and Javascript code""" - # format Python - install_requirements_file(session, "check-style") - session.run("black", ".") - session.run("isort", ".") - - # format client Javascript - session.chdir(CLIENT_DIR) - session.run("npm", "run", "format", external=True) - - # format docs Javascript - session.chdir(ROOT_DIR / "docs" / "source" / "_custom_js") - session.run("npm", "run", "format", external=True) - - -@group.session -def tsc(session: Session) -> None: - session.chdir(CLIENT_DIR) - session.run("npx", "tsc", "-b", "-w", "packages/app", external=True) - - -@group.session -def example(session: Session) -> None: - """Run an example""" - session.install("matplotlib") - install_reactpy_dev(session) - session.run( - "python", - "scripts/one_example.py", - *session.posargs, - env=get_reactpy_script_env(), - ) - - -@group.session -def docs(session: Session) -> None: - """Build and display documentation in the browser (automatically reloads on change)""" - install_requirements_file(session, "build-docs") - install_reactpy_dev(session) - session.run( - "python", - "scripts/live_docs.py", - "--open-browser", - # watch python source too - "--watch=src/reactpy", - # for some reason this matches absolute paths - "--ignore=**/_auto/*", - "--ignore=**/_static/custom.js", - "--ignore=**/node_modules/*", - "--ignore=**/package-lock.json", - "-a", - "-E", - "-b", - "html", - "docs/source", - "docs/build", - env={**os.environ, **get_reactpy_script_env()}, - ) - - -@group.session -def docs_in_docker(session: Session) -> None: - """Build a docker image for the documentation and run it to mimic production""" - session.run( - "docker", - "build", - ".", - "--file", - "docs/Dockerfile", - "--tag", - "reactpy-docs:latest", - external=True, - ) - session.run( - "docker", - "run", - "-it", - "-p", - "5000:5000", - "-e", - "DEBUG=1", - "--rm", - "reactpy-docs:latest", - external=True, - ) - - -@group.session -def check_python_tests( - session: Session, - no_cov: Annotated[bool, Option(help="turn off coverage checks")] = False, - headed: Annotated[bool, Option(help="run tests with a headed browser")] = False, - pytest: Annotated[Sequence[str], replace(REMAINING_ARGS, help="pytest args")] = (), -) -> None: - """Run the Python-based test suite""" - session.env["REACTPY_DEBUG_MODE"] = "1" - install_requirements_file(session, "test-env") - session.run("playwright", "install", "chromium") - - args = ["pytest", *pytest] - if headed: - args.append("--headed") - - if no_cov: - session.log("Coverage won't be checked") - session.install(".[all]") - else: - args = ["coverage", "run", "--source=src/reactpy", "--module", *args] - install_reactpy_dev(session) - - session.run(*args) - - if not no_cov: - session.run("coverage", "report") - - -@group.session -def check_python_types(session: Session) -> None: - """Perform a static type analysis of the Python codebase""" - install_requirements_file(session, "check-types") - install_requirements_file(session, "pkg-deps") - install_requirements_file(session, "pkg-extras") - session.run("mypy", "--version") - session.run("mypy", "--show-error-codes", "--strict", "src/reactpy") - session.run("mypy", "--show-error-codes", "noxfile.py") - - -@group.session -def check_python_format(session: Session) -> None: - """Check that Python style guidelines are being followed""" - install_requirements_file(session, "check-style") - session.run("flake8", "src/reactpy", "tests", "docs") - session.run("black", ".", "--check") - session.run("isort", ".", "--check-only") - - -@group.session -def check_python_build(session: Session) -> None: - """Test whether the Python package can be build for distribution""" - install_requirements_file(session, "build-pkg") - session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".") - - -@group.session -def check_docs(session: Session) -> None: - """Verify that the docs build and that doctests pass""" - install_requirements_file(session, "build-docs") - install_reactpy_dev(session) - session.run( - "sphinx-build", - "-a", # re-write all output files - "-T", # show full tracebacks - "-W", # turn warnings into errors - "--keep-going", # complete the build, but still report warnings as errors - "-b", - "html", - "docs/source", - "docs/build", - ) - session.run("sphinx-build", "-b", "doctest", "docs/source", "docs/build") - # ensure docker image build works too - session.run("docker", "build", ".", "--file", "docs/Dockerfile", external=True) - - -@group.session -def check_javascript_tests(session: Session) -> None: - session.run("npm", "run", "check:tests", external=True) - - -@group.session -def check_javascript_format(session: Session) -> None: - session.run("npm", "run", "check:format", external=True) - - -@group.session -def check_javascript_types(session: Session) -> None: - session.run("npm", "run", "build", external=True) - session.run("npm", "run", "check:types", external=True) - - -@group.session -def check_javascript_build(session: Session) -> None: - session.run("npm", "run", "build", external=True) - - -@group.session -def build_javascript(session: Session) -> None: - """Build javascript client code""" - session.chdir(CLIENT_DIR) - session.run("npm", "run", "build", external=True) - - -@group.session -def build_python(session: Session) -> None: - """Build python package dist""" - rmtree(str(ROOT_DIR / "build")) - rmtree(str(ROOT_DIR / "dist")) - install_requirements_file(session, "build-pkg") - session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".") - - -@group.session -def publish( - session: Session, - publish_dry_run: Annotated[ - bool, - Option(help="whether to test the release process"), - ] = False, - publish_fake_tags: Annotated[ - Sequence[str], - Option(nargs="*", type=str, help="fake tags to use for a dry run release"), - ] = (), -) -> None: - packages = get_packages(session) - - release_prep: dict[LanguageName, ReleasePrepFunc] = { - "js": prepare_javascript_release, - "py": prepare_python_release, - } - - if publish_fake_tags and not publish_dry_run: - session.error("Cannot specify --publish-fake-tags without --publish-dry-run") - - parsed_tags: list[TagInfo] = [] - for tag in publish_fake_tags or get_current_tags(session): - tag_info = parse_tag(tag) - if tag_info is None: - session.error( - f"Invalid tag {tag} - must be of the form --" - ) - parsed_tags.append(tag_info) # type: ignore - - publishers: list[tuple[Path, Callable[[bool], None]]] = [] - for tag, tag_pkg, tag_ver in parsed_tags: - if tag_pkg not in packages: - session.error(f"Tag {tag} references package {tag_pkg} that does not exist") - - pkg_name, pkg_path, pkg_lang, pkg_ver = pkg_info = packages[tag_pkg] - if pkg_ver != tag_ver: - session.error( - f"Tag {tag} references version {tag_ver} of package {tag_pkg}, " - f"but the current version is {pkg_ver}" - ) - - session.chdir(pkg_path) - session.log(f"Preparing {tag_pkg} for release...") - publishers.append((pkg_path, release_prep[pkg_lang](session, pkg_info))) - - for pkg_path, publish in publishers: - session.log(f"Publishing {pkg_path}...") - session.chdir(pkg_path) - publish(publish_dry_run) - - -# --- Utilities ------------------------------------------------------------------------ - - -def install_requirements_file(session: Session, name: str) -> None: - file_path = ROOT_DIR / "requirements" / (name + ".txt") - assert file_path.exists(), f"requirements file {file_path} does not exist" - session.install("-r", str(file_path)) - - -def install_reactpy_dev(session: Session, extras: str = "all") -> None: - if "--no-install" not in session.posargs: - session.install("-e", f".[{extras}]") - else: - session.posargs.remove("--no-install") - - -def get_reactpy_script_env() -> dict[str, str]: - return { - "PYTHONPATH": os.getcwd(), - "REACTPY_DEBUG_MODE": os.environ.get("REACTPY_DEBUG_MODE", "1"), - "REACTPY_TESTING_DEFAULT_TIMEOUT": os.environ.get( - "REACTPY_TESTING_DEFAULT_TIMEOUT", "6.0" - ), - "REACTPY_CHECK_VDOM_SPEC": os.environ.get("REACTPY_CHECK_VDOM_SPEC", "0"), - } - - -def prepare_javascript_release( - session: Session, package: PackageInfo -) -> Callable[[bool], None]: - node_auth_token = session.env.get("NODE_AUTH_TOKEN") - if node_auth_token is None: - session.error("NODE_AUTH_TOKEN environment variable must be set") - - session.run("npm", "ci", external=True) - session.run("npm", "run", "build", external=True) - - def publish(dry_run: bool) -> None: - if dry_run: - session.run( - "npm", - "--workspace", - package.name, - "pack", - "--dry-run", - external=True, - ) - return - session.run( - "npm", - "--workspace", - package.name, - "publish", - "--access", - "public", - external=True, - env={"NODE_AUTH_TOKEN": node_auth_token}, - ) - - return publish - - -def prepare_python_release( - session: Session, package: PackageInfo -) -> Callable[[bool], None]: - twine_username = session.env.get("PYPI_USERNAME") - twine_password = session.env.get("PYPI_PASSWORD") - - if not (twine_password and twine_username): - session.error( - "PYPI_USERNAME and PYPI_PASSWORD environment variables must be set" - ) - - for build_dir_name in ["build", "dist"]: - build_dir_path = Path.cwd() / build_dir_name - if build_dir_path.exists(): - rmtree(str(build_dir_path)) - - install_requirements_file(session, "build-pkg") - session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".") - - def publish(dry_run: bool): - if dry_run: - session.run("twine", "check", "dist/*") - return - - session.run( - "twine", - "upload", - "dist/*", - env={"TWINE_USERNAME": twine_username, "TWINE_PASSWORD": twine_password}, - ) - - return publish - - -def get_packages(session: Session) -> dict[str, PackageInfo]: - packages: dict[str, PackageInfo] = { - "reactpy": PackageInfo( - "reactpy", ROOT_DIR, "py", get_reactpy_package_version(session) - ) - } - - # collect javascript packages - js_package_paths: list[Path] = [] - for maybed_pkg in (CLIENT_DIR / "packages").glob("*"): - if not (maybed_pkg / "package.json").exists(): - for nmaybe_namespaced_pkg in maybed_pkg.glob("*"): - if (nmaybe_namespaced_pkg / "package.json").exists(): - js_package_paths.append(nmaybe_namespaced_pkg) - else: - js_package_paths.append(maybed_pkg) - - # get javascript package info - for pkg in js_package_paths: - pkg_json_file = pkg / "package.json" # we already know this exists - - pkg_json = json.loads(pkg_json_file.read_text()) - - pkg_name = pkg_json.get("name") - pkg_version = pkg_json.get("version") - - if pkg_version is None or pkg_name is None: - session.log(f"Skipping - {pkg_name} has no name/version in package.json") - continue - - if pkg_name in packages: - session.error(f"Duplicate package name {pkg_name}") - - packages[pkg_name] = PackageInfo(pkg_name, CLIENT_DIR, "js", pkg_version) - - return packages - - -class PackageInfo(NamedTuple): - name: str - path: Path - language: LanguageName - version: str - - -def get_current_tags(session: Session) -> list[str]: - """Get tags for the current commit""" - # check if unstaged changes - try: - session.run( - "git", - "diff", - "--cached", - "--exit-code", - silent=True, - external=True, - ) - session.run( - "git", - "diff", - "--exit-code", - silent=True, - external=True, - ) - except Exception: - session.error("Cannot create a tag - there are uncommited changes") - - tags_per_commit: dict[str, list[str]] = {} - for commit, tag in map( - str.split, - cast( - str, - session.run( - "git", - "for-each-ref", - "--format", - r"%(objectname) %(refname:short)", - "refs/tags", - silent=True, - external=True, - ), - ).splitlines(), - ): - tags_per_commit.setdefault(commit, []).append(tag) - - current_commit = cast( - str, session.run("git", "rev-parse", "HEAD", silent=True, external=True) - ).strip() - tags = tags_per_commit.get(current_commit, []) - - if not tags: - session.error("No tags found for current commit") - - session.log(f"Found tags: {tags}") - - return tags - - -def parse_tag(tag: str) -> TagInfo | None: - match = TAG_PATTERN.match(tag) - if not match: - return None - return TagInfo(tag, match["name"], match["version"]) - - -class TagInfo(NamedTuple): - tag: str - package: str - version: str - - -def get_reactpy_package_version(session: Session) -> str: # type: ignore[return] - pkg_root_init_file = REACTPY_DIR / "__init__.py" - for line in pkg_root_init_file.read_text().split("\n"): - if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'): - return ( - line - # get assignment value - .split("=", 1)[1] - # remove "DO NOT MODIFY" comment - .split("#", 1)[0] - # clean up leading/trailing space - .strip() - # remove the quotes - [1:-1] - ) - session.error(f"No version found in {pkg_root_init_file}") diff --git a/pyproject.toml b/pyproject.toml index f843140ea..c2845ea6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,61 +1,132 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[tool.isort] -multi_line_output = 3 -force_grid_wrap = 0 -use_parentheses = "True" -ensure_newline_before_comments = "True" -include_trailing_comma = "True" -line_length = 88 -lines_after_imports = 2 - -[tool.mypy] -incremental = false -ignore_missing_imports = true -warn_unused_configs = true -warn_redundant_casts = true -warn_unused_ignores = true - -[tool.pytest.ini_options] -testpaths = "tests" -xfail_strict = true -markers = ["slow: marks tests as slow (deselect with '-m \"not slow\"')"] -python_files = "*asserts.py test_*.py" -asyncio_mode = "auto" - -[tool.coverage.report] -fail_under = 100 -show_missing = true -skip_covered = true -sort = "Name" -exclude_lines = [ - # These are regex patterns - 'pragma: no cover', - '\.\.\.', - 'raise NotImplementedError', - 'if TYPE_CHECKING[\s:]', -] -omit = [ - "src/reactpy/__main__.py", +# --- Project ---------------------------------------------------------------------------- + +[project] +name = "scripts" +version = "0.0.0" +description = "Scripts for managing the ReactPy respository" + +# --- Hatch ---------------------------------------------------------------------------- + +[tool.hatch.envs.default] +detached = true +dependencies = [ + "invoke", + # lint + "black", + "ruff", + "toml", + "flake8", + "flake8-pyproject", + "reactpy-flake8 >=0.7", + # publish + "semver >=2, <3", + "twine", ] -[tool.pydocstyle] -inherit = false -match = '.*\.py' -convention = "google" -add_ignore = ["D100", "D101", "D102", "D103", "D104", "D105", "D107", "D412", "D415"] +[tool.hatch.envs.default.scripts] +publish = "invoke publish {args}" +docs = "invoke docs {args}" + +lint-py = "invoke lint-py {args}" +lint-js = "invoke lint-js {args}" + +test-py = "invoke test-py {args}" +test-js = "invoke test-js" +test-docs = "invoke test-docs" + +# --- Black ---------------------------------------------------------------------------- + +[tool.black] +target-version = ["py39"] +line-length = 88 + +# --- Flake8 ---------------------------------------------------------------------------- [tool.flake8] -ignore = ["E203", "E266", "E501", "W503", "F811", "N802", "N806"] -per-file-ignores = [ - # sometimes this is required in order to hide setup for an example - "docs/*/_examples/*.py:E402", +select = ["RPY"] # only need to check with reactpy-flake8 +exclude = ["**/node_modules/*", ".eggs/*", ".tox/*", "**/venv/*"] + +# --- Ruff ----------------------------------------------------------------------------- + +[tool.ruff] +target-version = "py39" +line-length = 88 +select = [ + "A", + "ARG", + "B", + "C", + "DTZ", + "E", + # error message linting is overkill + # "EM", + "F", + # TODO: turn this on later + # "FBT", + "I", + "ICN", + "ISC", + "N", + "PLC", + "PLE", + "PLR", + "PLW", + "Q", + "RUF", + "S", + "T", + "TID", + "UP", + "W", + "YTT", +] +ignore = [ + # TODO: turn this on later + "N802", "N806", # allow TitleCase functions/variables + # We're not any cryptography + "S311", + # For loop variable re-assignment seems like an uncommon mistake + "PLW2901", + # Let Black deal with line-length + "E501", + # Allow args/attrs to shadow built-ins + "A002", "A003", + # Allow unused args (useful for documenting what the parameter is for later) + "ARG001", "ARG002", "ARG005", + # Allow non-abstract empty methods in abstract base classes + "B027", + # Allow boolean positional values in function calls, like `dict.get(... True)` + "FBT003", + # If we're making an explicit comparison to a falsy value it was probably intentional + "PLC1901", + # Ignore checks for possible passwords + "S105", "S106", "S107", + # Ignore complexity + "C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915", +] +unfixable = [ + # Don't touch unused imports + "F401", +] + +[tool.ruff.isort] +known-first-party = ["reactpy"] + +[tool.ruff.flake8-tidy-imports] +ban-relative-imports = "all" + +[tool.ruff.per-file-ignores] +# Tests can use magic values, assertions, and relative imports +"**/tests/**/*" = ["PLR2004", "S101", "TID252"] +"docs/**/*.py" = [ + # Examples require some extra setup before import + "E402", + # Allow exec + "S102", + # Allow print + "T201", +] +"scripts/**/*.py" = [ + # Allow print + "T201", ] -max-line-length = 88 -max-complexity = 20 -select = ["B", "C", "E", "F", "W", "T4", "B9", "N", "RPY"] -exclude = ["**/node_modules/*", ".eggs/*", ".tox/*"] -# -- flake8-tidy-imports -- -ban-relative-imports = "true" diff --git a/requirements/build-docs.txt b/requirements/build-docs.txt deleted file mode 100644 index 274cbb56b..000000000 --- a/requirements/build-docs.txt +++ /dev/null @@ -1,9 +0,0 @@ -sphinx -sphinx-autodoc-typehints -furo ==2022.04.07 -sphinx-copybutton -sphinx-autobuild -sphinx-reredirects -sphinx-design -sphinx-resolve-py-references -sphinxext-opengraph diff --git a/requirements/build-pkg.txt b/requirements/build-pkg.txt deleted file mode 100644 index 82f40eafa..000000000 --- a/requirements/build-pkg.txt +++ /dev/null @@ -1,3 +0,0 @@ -twine -wheel -build diff --git a/requirements/check-style.txt b/requirements/check-style.txt deleted file mode 100644 index 035e52138..000000000 --- a/requirements/check-style.txt +++ /dev/null @@ -1,10 +0,0 @@ -black[jupyter] -flake8 -flake8-pyproject -reactpy-flake8 >=0.7 -flake8-print -flake8-tidy-imports -isort >=5.7.0 -pep8-naming -# pydocstyle -git+https://github.com/PyCQA/pydocstyle.git@bd4993345a241bd0d5128f21de56394b1cde3714#egg=pydocstyle diff --git a/requirements/check-types.txt b/requirements/check-types.txt deleted file mode 100644 index 2ae26d60a..000000000 --- a/requirements/check-types.txt +++ /dev/null @@ -1,6 +0,0 @@ -mypy -types-click -types-tornado -types-pkg-resources -types-flask -types-requests diff --git a/requirements/make-release.txt b/requirements/make-release.txt deleted file mode 100644 index da7b791ef..000000000 --- a/requirements/make-release.txt +++ /dev/null @@ -1 +0,0 @@ -semver >=2, <3 diff --git a/requirements/nox-deps.txt b/requirements/nox-deps.txt deleted file mode 100644 index 218b48093..000000000 --- a/requirements/nox-deps.txt +++ /dev/null @@ -1,2 +0,0 @@ -nox -noxopt diff --git a/requirements/pkg-deps.txt b/requirements/pkg-deps.txt deleted file mode 100644 index 885fa4828..000000000 --- a/requirements/pkg-deps.txt +++ /dev/null @@ -1,9 +0,0 @@ -typing-extensions >=3.10 -mypy-extensions >=0.4.3 -anyio >=3 -jsonpatch >=1.32 -fastjsonschema >=2.14.5 -requests >=2 -colorlog >=6 -asgiref >=3 -lxml >=4 diff --git a/requirements/pkg-extras.txt b/requirements/pkg-extras.txt deleted file mode 100644 index 70edde6d5..000000000 --- a/requirements/pkg-extras.txt +++ /dev/null @@ -1,24 +0,0 @@ -# extra=starlette -starlette >=0.13.6 -uvicorn[standard] >=0.19.0 - -# extra=sanic -sanic >=21 -sanic-cors -uvicorn[standard] >=0.19.0 - -# extra=fastapi -fastapi >=0.63.0 -uvicorn[standard] >=0.19.0 - -# extra=flask -flask -markupsafe>=1.1.1,<2.1 -flask-cors -flask-sock - -# extra=tornado -tornado - -# extra=testing -playwright diff --git a/requirements/test-env.txt b/requirements/test-env.txt deleted file mode 100644 index ac59ea45b..000000000 --- a/requirements/test-env.txt +++ /dev/null @@ -1,15 +0,0 @@ -pytest -pytest-asyncio>=0.17 -pytest-mock -pytest-rerunfailures -pytest-timeout - -coverage -responses -playwright - -# I'm not quite sure why this needs to be installed for tests with Sanic to pass -sanic-testing - -# Used to generate model changes from layout update messages -jsonpointer diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index a9e8fa35c..000000000 --- a/scripts/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Scripts - -All scripts should be run from the repository root (typically using -[`nox`](https://nox.thea.codes/en/stable/)). Use `nox --list` to see the list of -available scripts. diff --git a/scripts/one_example.py b/scripts/one_example.py deleted file mode 100644 index 079bd7a6a..000000000 --- a/scripts/one_example.py +++ /dev/null @@ -1,103 +0,0 @@ -import sys -import time -from os.path import getmtime -from threading import Event, Thread - -import reactpy -from docs.examples import all_example_names, get_example_files_by_name, load_one_example -from reactpy.widgets import _hotswap - - -EXAMPLE_NAME_SET = all_example_names() -EXAMPLE_NAME_LIST = tuple(sorted(EXAMPLE_NAME_SET)) - - -def on_file_change(path, callback): - did_call_back_once = Event() - - def watch_for_change(): - last_modified = 0 - while True: - modified_at = getmtime(path) - if modified_at != last_modified: - callback() - did_call_back_once.set() - last_modified = modified_at - time.sleep(1) - - Thread(target=watch_for_change, daemon=True).start() - did_call_back_once.wait() - - -def main(): - ex_name = _example_name_input() - - mount, component = _hotswap() - - def update_component(): - print(f"Loading example: {ex_name!r}") - mount(load_one_example(ex_name)) - - for file in get_example_files_by_name(ex_name): - on_file_change(file, update_component) - - reactpy.run(component) - - -def _example_name_input() -> str: - if len(sys.argv) == 1: - _print_error( - "No example argument given. Provide an example's number from above." - ) - sys.exit(1) - - ex_num = sys.argv[1] - - try: - ex_num = int(ex_num) - except ValueError: - _print_error( - f"No example {ex_num!r} exists. Provide an example's number as an integer." - ) - sys.exit(1) - - ex_index = ex_num - 1 - try: - return EXAMPLE_NAME_LIST[ex_index] - except IndexError: - _print_error(f"No example #{ex_num} exists. Choose from an option above.") - sys.exit(1) - - -def _print_error(*args) -> None: - _print_available_options() - print(*args) - - -def _print_available_options(): - examples_by_path = {} - for i, name in enumerate(EXAMPLE_NAME_LIST): - if "/" not in name: - path = "" - else: - path, name = name.rsplit("/", 1) - examples_by_path.setdefault(path, []).append(name) - - number = 1 - print() - for path, names in examples_by_path.items(): - title = " > ".join( - section.replace("-", " ").replace("_", " ").title() - for section in path.split("/") - if not section.startswith("_") - ) - print(title) - print("-" * len(title)) - for name in names: - print(f"{number}. ", name) - number += 1 - print() - - -if __name__ == "__main__": - main() diff --git a/scripts/run_docs.py b/scripts/run_docs.py deleted file mode 100644 index 31c70a038..000000000 --- a/scripts/run_docs.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -import sys - - -# all scripts should be run from the repository root so we need to insert cwd to path -# to import docs -sys.path.insert(0, os.getcwd()) - -from docs.app import make_app - - -app = make_app() - -if __name__ == "__main__": - app.run( - host="0.0.0.0", - port=int(os.environ.get("PORT", 5000)), - workers=int(os.environ.get("WEB_CONCURRENCY", 1)), - debug=bool(int(os.environ.get("DEBUG", "0"))), - ) diff --git a/setup.py b/setup.py deleted file mode 100644 index 5f256008f..000000000 --- a/setup.py +++ /dev/null @@ -1,203 +0,0 @@ -from __future__ import print_function - -import pipes -import shutil -import subprocess -import sys -import traceback -from logging import StreamHandler, getLogger -from pathlib import Path - -from setuptools import find_packages, setup -from setuptools.command.develop import develop -from setuptools.command.sdist import sdist - - -if sys.platform == "win32": - from subprocess import list2cmdline -else: - - def list2cmdline(cmd_list): - return " ".join(map(pipes.quote, cmd_list)) - - -log = getLogger() -log.addHandler(StreamHandler(sys.stdout)) - - -# -------------------------------------------------------------------------------------- -# Basic Constants -# -------------------------------------------------------------------------------------- - - -# the name of the project -NAME = "reactpy" - -# basic paths used to gather files -ROOT_DIR = Path(__file__).parent -SRC_DIR = ROOT_DIR / "src" -PKG_DIR = SRC_DIR / NAME -JS_DIR = SRC_DIR / "client" - - -# -------------------------------------------------------------------------------------- -# Package Definition -# -------------------------------------------------------------------------------------- - - -package = { - "name": NAME, - "python_requires": ">=3.7", - "packages": find_packages(str(SRC_DIR)), - "package_dir": {"": "src"}, - "description": "It's React, but in Python", - "author": "Ryan Morshead", - "author_email": "ryan.morshead@gmail.com", - "url": "https://github.com/reactive-python/reactpy", - "license": "MIT", - "platforms": "Linux, Mac OS X, Windows", - "keywords": ["interactive", "widgets", "DOM", "React"], - "include_package_data": True, - "zip_safe": False, - "classifiers": [ - "Environment :: Web Environment", - "Framework :: AsyncIO", - "Framework :: Flask", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Topic :: Software Development :: User Interfaces", - "Topic :: Software Development :: Widget Sets", - "Typing :: Typed", - ], -} - - -# -------------------------------------------------------------------------------------- -# Library Version -# -------------------------------------------------------------------------------------- - -pkg_root_init_file = PKG_DIR / "__init__.py" -for line in pkg_root_init_file.read_text().split("\n"): - if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'): - package["version"] = ( - line - # get assignment value - .split("=", 1)[1] - # remove "DO NOT MODIFY" comment - .split("#", 1)[0] - # clean up leading/trailing space - .strip() - # remove the quotes - [1:-1] - ) - break -else: - print(f"No version found in {pkg_root_init_file}") - sys.exit(1) - - -# -------------------------------------------------------------------------------------- -# Requirements -# -------------------------------------------------------------------------------------- - - -requirements = [] -with (ROOT_DIR / "requirements" / "pkg-deps.txt").open() as f: - for line in map(str.strip, f): - if not line.startswith("#"): - requirements.append(line) -package["install_requires"] = requirements - -_current_extras = [] -extra_requirements = {"all": []} # type: ignore -extra_requirements_path = ROOT_DIR / "requirements" / "pkg-extras.txt" -with extra_requirements_path.open() as f: - for line in map(str.strip, f): - if line.startswith("#") and line[1:].strip().startswith("extra="): - _current_extras = [e.strip() for e in line.split("=", 1)[1].split(",")] - if "all" in _current_extras: - raise ValueError("%r uses the reserved extra name 'all'") - for e in _current_extras: - extra_requirements[e] = [] - elif _current_extras: - for e in _current_extras: - extra_requirements[e].append(line) - extra_requirements["all"].append(line) - elif line: - raise ValueError( - f"No '# extra=' header before requirements in {extra_requirements_path}" - ) -package["extras_require"] = extra_requirements - - -# -------------------------------------------------------------------------------------- -# Library Description -# -------------------------------------------------------------------------------------- - - -with (ROOT_DIR / "README.md").open() as f: - long_description = f.read() - -package["long_description"] = long_description -package["long_description_content_type"] = "text/markdown" - - -# -------------------------------------------------------------------------------------- -# Command Line Interface -# -------------------------------------------------------------------------------------- - -package["entry_points"] = {"console_scripts": ["reactpy=reactpy.__main__:app"]} - -# -------------------------------------------------------------------------------------- -# Build Javascript -# -------------------------------------------------------------------------------------- - - -def build_javascript_first(cls): - class Command(cls): - def run(self): - log.info("Installing Javascript...") - try: - npm = shutil.which("npm") # this is required on windows - if npm is None: - raise RuntimeError("NPM is not installed.") - for args in (f"{npm} ci", f"{npm} run build"): - args_list = args.split() - log.info(f"> {list2cmdline(args_list)}") - subprocess.run(args_list, cwd=str(JS_DIR), check=True) - except Exception: - log.error("Failed to install Javascript") - log.error(traceback.format_exc()) - raise - else: - log.info("Successfully installed Javascript") - super().run() - - return Command - - -package["cmdclass"] = { - "sdist": build_javascript_first(sdist), - "develop": build_javascript_first(develop), -} - -if sys.version_info < (3, 10, 6): - from distutils.command.build import build - - package["cmdclass"]["build"] = build_javascript_first(build) -else: - from setuptools.command.build_py import build_py - - package["cmdclass"]["build_py"] = build_javascript_first(build_py) - - -# -------------------------------------------------------------------------------------- -# Install It -# -------------------------------------------------------------------------------------- - - -if __name__ == "__main__": - setup(**package) diff --git a/src/client/.eslintrc.json b/src/js/.eslintrc.json similarity index 100% rename from src/client/.eslintrc.json rename to src/js/.eslintrc.json diff --git a/src/client/.gitignore b/src/js/.gitignore similarity index 91% rename from src/client/.gitignore rename to src/js/.gitignore index 52128d3e1..fedd7ea26 100644 --- a/src/client/.gitignore +++ b/src/js/.gitignore @@ -1,2 +1,3 @@ tsconfig.tsbuildinfo packages/**/package-lock.json +dist diff --git a/src/client/README.md b/src/js/README.md similarity index 100% rename from src/client/README.md rename to src/js/README.md diff --git a/src/client/ui/.eslintrc.json b/src/js/app/.eslintrc.json similarity index 100% rename from src/client/ui/.eslintrc.json rename to src/js/app/.eslintrc.json diff --git a/src/client/ui/index.html b/src/js/app/index.html similarity index 100% rename from src/client/ui/index.html rename to src/js/app/index.html diff --git a/src/client/ui/package-lock.json b/src/js/app/package-lock.json similarity index 100% rename from src/client/ui/package-lock.json rename to src/js/app/package-lock.json diff --git a/src/client/ui/package.json b/src/js/app/package.json similarity index 94% rename from src/client/ui/package.json rename to src/js/app/package.json index 7c1e04a05..b9371dba3 100644 --- a/src/client/ui/package.json +++ b/src/js/app/package.json @@ -22,7 +22,6 @@ "build": "vite build", "format": "prettier --write . && eslint --fix .", "test": "npm run check:tests", - "check:format": "prettier --check .", "check:tests": "echo 'no tests'", "check:types": "tsc --noEmit" } diff --git a/src/client/ui/public/assets/reactpy-logo.ico b/src/js/app/public/assets/reactpy-logo.ico similarity index 100% rename from src/client/ui/public/assets/reactpy-logo.ico rename to src/js/app/public/assets/reactpy-logo.ico diff --git a/src/client/ui/src/index.ts b/src/js/app/src/index.ts similarity index 100% rename from src/client/ui/src/index.ts rename to src/js/app/src/index.ts diff --git a/src/client/ui/tsconfig.json b/src/js/app/tsconfig.json similarity index 100% rename from src/client/ui/tsconfig.json rename to src/js/app/tsconfig.json diff --git a/src/client/ui/vite.config.js b/src/js/app/vite.config.js similarity index 75% rename from src/client/ui/vite.config.js rename to src/js/app/vite.config.js index 6919d8e1c..c97fb6dac 100644 --- a/src/client/ui/vite.config.js +++ b/src/js/app/vite.config.js @@ -1,7 +1,7 @@ import { defineConfig } from "vite"; export default defineConfig({ - build: { outDir: "../../reactpy/_client", emptyOutDir: true }, + build: { emptyOutDir: true }, resolve: { alias: { react: "preact/compat", diff --git a/src/client/package-lock.json b/src/js/package-lock.json similarity index 98% rename from src/client/package-lock.json rename to src/js/package-lock.json index 109324f5a..2edfdd260 100644 --- a/src/client/package-lock.json +++ b/src/js/package-lock.json @@ -1,5 +1,5 @@ { - "name": "client", + "name": "js", "lockfileVersion": 2, "requires": true, "packages": { @@ -8,7 +8,7 @@ "workspaces": [ "packages/event-to-object", "packages/@reactpy/client", - "ui" + "app" ], "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.58.0", @@ -18,6 +18,45 @@ "prettier": "^3.0.0-alpha.6" } }, + "app": { + "license": "MIT", + "dependencies": { + "@reactpy/client": "^0.2.0", + "preact": "^10.7.0" + }, + "devDependencies": { + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "typescript": "^4.9.5", + "vite": "^3.1.8" + } + }, + "app/node_modules/@reactpy/client": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz", + "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==", + "dependencies": { + "event-to-object": "^0.1.2", + "json-pointer": "^0.6.2" + }, + "peerDependencies": { + "react": ">=16 <18", + "react-dom": ">=16 <18" + } + }, + "app/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "apps/ui": { "extraneous": true, "license": "MIT", @@ -514,6 +553,10 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/app": { + "resolved": "app", + "link": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2386,10 +2429,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2663,9 +2712,9 @@ } }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", "dev": true, "funding": [ { @@ -2675,10 +2724,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -2687,9 +2740,9 @@ } }, "node_modules/preact": { - "version": "10.13.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz", - "integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ==", + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz", + "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -2810,12 +2863,12 @@ } }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -3232,10 +3285,6 @@ "node": ">=12.20" } }, - "node_modules/ui": { - "resolved": "ui", - "link": true - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -3279,9 +3328,9 @@ } }, "node_modules/vite": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", - "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz", + "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==", "dev": true, "dependencies": { "esbuild": "^0.15.9", @@ -3458,7 +3507,7 @@ } }, "packages/@reactpy/client": { - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { "event-to-object": "^0.1.2", @@ -3556,6 +3605,7 @@ "extraneous": true }, "ui": { + "extraneous": true, "license": "MIT", "dependencies": { "@reactpy/client": "^0.2.0", @@ -3567,32 +3617,6 @@ "typescript": "^4.9.5", "vite": "^3.1.8" } - }, - "ui/node_modules/@reactpy/client": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz", - "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==", - "dependencies": { - "event-to-object": "^0.1.2", - "json-pointer": "^0.6.2" - }, - "peerDependencies": { - "react": ">=16 <18", - "react-dom": ">=16 <18" - } - }, - "ui/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } } }, "dependencies": { @@ -3923,6 +3947,34 @@ "color-convert": "^2.0.1" } }, + "app": { + "version": "file:app", + "requires": { + "@reactpy/client": "^0.2.0", + "@types/react": "^17.0", + "@types/react-dom": "^17.0", + "preact": "^10.7.0", + "typescript": "^4.9.5", + "vite": "^3.1.8" + }, + "dependencies": { + "@reactpy/client": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz", + "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==", + "requires": { + "event-to-object": "^0.1.2", + "json-pointer": "^0.6.2" + } + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5233,9 +5285,9 @@ "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true }, "natural-compare": { @@ -5424,20 +5476,20 @@ "dev": true }, "postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", "dev": true, "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "preact": { - "version": "10.13.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.1.tgz", - "integrity": "sha512-KyoXVDU5OqTpG9LXlB3+y639JAGzl8JSBXLn1J9HTSB3gbKcuInga7bZnXLlxmK94ntTs1EFeZp0lrja2AuBYQ==" + "version": "10.15.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz", + "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==" }, "prelude-ls": { "version": "1.2.1", @@ -5513,12 +5565,12 @@ } }, "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, "requires": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -5802,34 +5854,6 @@ "dev": true, "peer": true }, - "ui": { - "version": "file:ui", - "requires": { - "@reactpy/client": "^0.2.0", - "@types/react": "^17.0", - "@types/react-dom": "^17.0", - "preact": "^10.7.0", - "typescript": "^4.9.5", - "vite": "^3.1.8" - }, - "dependencies": { - "@reactpy/client": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz", - "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==", - "requires": { - "event-to-object": "^0.1.2", - "json-pointer": "^0.6.2" - } - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true - } - } - }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -5864,9 +5888,9 @@ } }, "vite": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", - "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz", + "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==", "dev": true, "requires": { "esbuild": "^0.15.9", diff --git a/src/client/package.json b/src/js/package.json similarity index 62% rename from src/client/package.json rename to src/js/package.json index 3992d59ed..dc6dc7fca 100644 --- a/src/client/package.json +++ b/src/js/package.json @@ -4,15 +4,17 @@ "publish": "npm --workspaces publish", "test": "npm --workspaces test", "build": "npm --workspaces run build", - "format": "prettier --write . && eslint --fix .", - "check:format": "prettier --check . && eslint .", + "format": "npm run prettier -- --write && npm run eslint -- --fix", + "check:format": "npm run prettier -- --check && npm run eslint", "check:tests": "npm --workspaces run check:tests", - "check:types": "npm --workspaces run check:types" + "check:types": "npm --workspaces run check:types", + "prettier": "prettier --ignore-path .gitignore .", + "eslint": "eslint --ignore-path .gitignore ." }, "workspaces": [ "packages/event-to-object", "packages/@reactpy/client", - "ui" + "app" ], "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.58.0", diff --git a/src/client/packages/@reactpy/client/.gitignore b/src/js/packages/@reactpy/client/.gitignore similarity index 100% rename from src/client/packages/@reactpy/client/.gitignore rename to src/js/packages/@reactpy/client/.gitignore diff --git a/src/client/packages/@reactpy/client/README.md b/src/js/packages/@reactpy/client/README.md similarity index 100% rename from src/client/packages/@reactpy/client/README.md rename to src/js/packages/@reactpy/client/README.md diff --git a/src/client/packages/@reactpy/client/package.json b/src/js/packages/@reactpy/client/package.json similarity index 100% rename from src/client/packages/@reactpy/client/package.json rename to src/js/packages/@reactpy/client/package.json diff --git a/src/client/packages/@reactpy/client/src/components.tsx b/src/js/packages/@reactpy/client/src/components.tsx similarity index 100% rename from src/client/packages/@reactpy/client/src/components.tsx rename to src/js/packages/@reactpy/client/src/components.tsx diff --git a/src/client/packages/@reactpy/client/src/index.ts b/src/js/packages/@reactpy/client/src/index.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/index.ts rename to src/js/packages/@reactpy/client/src/index.ts diff --git a/src/client/packages/@reactpy/client/src/logger.ts b/src/js/packages/@reactpy/client/src/logger.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/logger.ts rename to src/js/packages/@reactpy/client/src/logger.ts diff --git a/src/client/packages/@reactpy/client/src/messages.ts b/src/js/packages/@reactpy/client/src/messages.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/messages.ts rename to src/js/packages/@reactpy/client/src/messages.ts diff --git a/src/client/packages/@reactpy/client/src/mount.tsx b/src/js/packages/@reactpy/client/src/mount.tsx similarity index 100% rename from src/client/packages/@reactpy/client/src/mount.tsx rename to src/js/packages/@reactpy/client/src/mount.tsx diff --git a/src/client/packages/@reactpy/client/src/reactpy-client.ts b/src/js/packages/@reactpy/client/src/reactpy-client.ts similarity index 100% rename from src/client/packages/@reactpy/client/src/reactpy-client.ts rename to src/js/packages/@reactpy/client/src/reactpy-client.ts diff --git a/src/client/packages/@reactpy/client/src/reactpy-vdom.tsx b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx similarity index 100% rename from src/client/packages/@reactpy/client/src/reactpy-vdom.tsx rename to src/js/packages/@reactpy/client/src/reactpy-vdom.tsx diff --git a/src/client/packages/@reactpy/client/tsconfig.json b/src/js/packages/@reactpy/client/tsconfig.json similarity index 100% rename from src/client/packages/@reactpy/client/tsconfig.json rename to src/js/packages/@reactpy/client/tsconfig.json diff --git a/src/client/packages/event-to-object/package.json b/src/js/packages/event-to-object/package.json similarity index 100% rename from src/client/packages/event-to-object/package.json rename to src/js/packages/event-to-object/package.json diff --git a/src/client/packages/event-to-object/src/events.ts b/src/js/packages/event-to-object/src/events.ts similarity index 100% rename from src/client/packages/event-to-object/src/events.ts rename to src/js/packages/event-to-object/src/events.ts diff --git a/src/client/packages/event-to-object/src/index.ts b/src/js/packages/event-to-object/src/index.ts similarity index 100% rename from src/client/packages/event-to-object/src/index.ts rename to src/js/packages/event-to-object/src/index.ts diff --git a/src/client/packages/event-to-object/tests/event-to-object.test.ts b/src/js/packages/event-to-object/tests/event-to-object.test.ts similarity index 100% rename from src/client/packages/event-to-object/tests/event-to-object.test.ts rename to src/js/packages/event-to-object/tests/event-to-object.test.ts diff --git a/src/client/packages/event-to-object/tests/tooling/check.ts b/src/js/packages/event-to-object/tests/tooling/check.ts similarity index 100% rename from src/client/packages/event-to-object/tests/tooling/check.ts rename to src/js/packages/event-to-object/tests/tooling/check.ts diff --git a/src/client/packages/event-to-object/tests/tooling/mock.ts b/src/js/packages/event-to-object/tests/tooling/mock.ts similarity index 100% rename from src/client/packages/event-to-object/tests/tooling/mock.ts rename to src/js/packages/event-to-object/tests/tooling/mock.ts diff --git a/src/client/packages/event-to-object/tests/tooling/setup.js b/src/js/packages/event-to-object/tests/tooling/setup.js similarity index 100% rename from src/client/packages/event-to-object/tests/tooling/setup.js rename to src/js/packages/event-to-object/tests/tooling/setup.js diff --git a/src/client/packages/event-to-object/tsconfig.json b/src/js/packages/event-to-object/tsconfig.json similarity index 100% rename from src/client/packages/event-to-object/tsconfig.json rename to src/js/packages/event-to-object/tsconfig.json diff --git a/src/client/packages/event-to-object/tsconfig.tests.json b/src/js/packages/event-to-object/tsconfig.tests.json similarity index 100% rename from src/client/packages/event-to-object/tsconfig.tests.json rename to src/js/packages/event-to-object/tsconfig.tests.json diff --git a/src/client/tsconfig.package.json b/src/js/tsconfig.package.json similarity index 100% rename from src/client/tsconfig.package.json rename to src/js/tsconfig.package.json diff --git a/src/py/reactpy/.gitignore b/src/py/reactpy/.gitignore new file mode 100644 index 000000000..0499d7590 --- /dev/null +++ b/src/py/reactpy/.gitignore @@ -0,0 +1,4 @@ +.coverage.* + +# --- Build Artifacts --- +reactpy/_static diff --git a/MANIFEST.in b/src/py/reactpy/MANIFEST.in similarity index 100% rename from MANIFEST.in rename to src/py/reactpy/MANIFEST.in diff --git a/src/py/reactpy/README.md b/src/py/reactpy/README.md new file mode 100644 index 000000000..910a573a5 --- /dev/null +++ b/src/py/reactpy/README.md @@ -0,0 +1,23 @@ +# ReactPy + +

+ + + + + + + + + + + + + + + +

+ +--- + +[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components that look and behave similar to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions. diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml new file mode 100644 index 000000000..659ddbf94 --- /dev/null +++ b/src/py/reactpy/pyproject.toml @@ -0,0 +1,175 @@ +[build-system] +requires = ["hatchling", "hatch-build-scripts>=0.0.4"] +build-backend = "hatchling.build" + +# --- Project -------------------------------------------------------------------------- + +[project] +name = "reactpy" +dynamic = ["version"] +description = 'Reactive user interfaces with pure Python' +readme = "README.md" +requires-python = ">=3.9" +license = "MIT" +keywords = ["react", "javascript", "reactpy", "component"] +authors = [ + { name = "Ryan Morshead", email = "ryan.morshead@gmail.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [ + "typing-extensions >=3.10", + "mypy-extensions >=0.4.3", + "anyio >=3", + "jsonpatch >=1.32", + "fastjsonschema >=2.14.5", + "requests >=2", + "colorlog >=6", + "asgiref >=3", + "lxml >=4", +] +[project.optional-dependencies] +all = ["reactpy[starlette,sanic,fastapi,flask,tornado,testing]"] + +starlette = [ + "starlette >=0.13.6", + "uvicorn[standard] >=0.19.0", +] +sanic = [ + "sanic >=21", + "sanic-cors", + "uvicorn[standard] >=0.19.0", +] +fastapi = [ + "fastapi >=0.63.0", + "uvicorn[standard] >=0.19.0", +] +flask = [ + "flask", + "markupsafe>=1.1.1,<2.1", + "flask-cors", + "flask-sock", +] +tornado = [ + "tornado", +] +testing = [ + "playwright", +] + +[project.urls] +Source = "https://github.com/reactive-python/reactpy" +Documentation = "https://github.com/reactive-python/reactpy#readme" +Issues = "https://github.com/reactive-python/reactpy/discussions" + +# --- Hatch ---------------------------------------------------------------------------- + +[tool.hatch.version] +path = "reactpy/__init__.py" + +[tool.hatch.envs.default] +features = ["all"] +pre-install-command = "hatch build --hooks-only" +dependencies = [ + "coverage[toml]>=6.5", + "pytest", + "pytest-asyncio>=0.17", + "pytest-mock", + "pytest-rerunfailures", + "pytest-timeout", + "responses", + "playwright", + # I'm not quite sure why this needs to be installed for tests with Sanic to pass + "sanic-testing", + # Used to generate model changes from layout update messages + "jsonpointer", +] +[tool.hatch.envs.default.scripts] +test = "playwright install && pytest {args:tests}" +test-cov = "playwright install && coverage run -m pytest {args:tests}" +cov-report = [ + # "- coverage combine", + "coverage report", +] +cov = [ + "test-cov {args}", + "cov-report", +] + +[tool.hatch.envs.default.env-vars] +REACTPY_DEBUG_MODE="1" + +[tool.hatch.envs.lint] +features = ["all"] +dependencies = [ + "mypy>=1.0.0", + "types-click", + "types-tornado", + "types-pkg-resources", + "types-flask", + "types-requests", +] + +[tool.hatch.envs.lint.scripts] +types = "mypy --strict reactpy" +all = ["types"] + +[[tool.hatch.build.hooks.build-scripts.scripts]] +work_dir = "../../js" +out_dir = "reactpy/_static" +commands = [ + "npm ci", + "npm run build" +] +artifacts = [ + "app/dist/" +] + +# --- Pytest --------------------------------------------------------------------------- + +[tool.pytest.ini_options] +testpaths = "tests" +xfail_strict = true +python_files = "*asserts.py test_*.py" +asyncio_mode = "auto" + +# --- MyPy ----------------------------------------------------------------------------- + +[tool.mypy] +incremental = false +ignore_missing_imports = true +warn_unused_configs = true +warn_redundant_casts = true +warn_unused_ignores = true + +# --- Coverage ------------------------------------------------------------------------- + +[tool.coverage.run] +source_pkgs = ["reactpy"] +branch = false +parallel = false +omit = [ + "reactpy/__init__.py", +] + +[tool.coverage.report] +fail_under = 100 +show_missing = true +skip_covered = true +sort = "Name" +exclude_lines = [ + "no ?cov", + '\.\.\.', + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] +omit = [ + "reactpy/__main__.py", +] diff --git a/src/reactpy/__init__.py b/src/py/reactpy/reactpy/__init__.py similarity index 99% rename from src/reactpy/__init__.py rename to src/py/reactpy/reactpy/__init__.py index c9adf6cb2..996a984b2 100644 --- a/src/reactpy/__init__.py +++ b/src/py/reactpy/reactpy/__init__.py @@ -20,7 +20,6 @@ from reactpy.core.vdom import vdom from reactpy.utils import Ref, html_to_vdom, vdom_to_html - __author__ = "The Reactive Python Team" __version__ = "1.0.0" # DO NOT MODIFY diff --git a/src/reactpy/__main__.py b/src/py/reactpy/reactpy/__main__.py similarity index 100% rename from src/reactpy/__main__.py rename to src/py/reactpy/reactpy/__main__.py diff --git a/src/reactpy/_console/__init__.py b/src/py/reactpy/reactpy/_console/__init__.py similarity index 100% rename from src/reactpy/_console/__init__.py rename to src/py/reactpy/reactpy/_console/__init__.py diff --git a/src/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py similarity index 93% rename from src/reactpy/_console/ast_utils.py rename to src/py/reactpy/reactpy/_console/ast_utils.py index aeaf8f512..180cb0e4b 100644 --- a/src/reactpy/_console/ast_utils.py +++ b/src/py/reactpy/reactpy/_console/ast_utils.py @@ -1,13 +1,13 @@ from __future__ import annotations import ast -from collections.abc import Sequence +from collections.abc import Iterator, Sequence from dataclasses import dataclass from pathlib import Path from textwrap import indent from tokenize import COMMENT as COMMENT_TOKEN from tokenize import generate_tokens -from typing import Any, Iterator +from typing import Any import click @@ -37,12 +37,13 @@ def rewrite_changed_nodes( ): nodes_to_unparse.append(current_node) break - else: # pragma: no cover - raise RuntimeError("Failed to change code") + else: # nocov + msg = "Failed to change code" + raise RuntimeError(msg) # check if an nodes to rewrite contain eachother, pick outermost nodes - current_outermost_node, *sorted_nodes_to_unparse = list( - sorted(nodes_to_unparse, key=lambda n: n.lineno) + current_outermost_node, *sorted_nodes_to_unparse = sorted( + nodes_to_unparse, key=lambda n: n.lineno ) outermost_nodes_to_unparse = [current_outermost_node] for node in sorted_nodes_to_unparse: @@ -80,7 +81,7 @@ def rewrite_changed_nodes( if comments: moved_comment_lines_from_end.append(len(lines) - node.lineno) - for lineno_from_end in sorted(list(set(moved_comment_lines_from_end))): + for lineno_from_end in sorted(set(moved_comment_lines_from_end)): click.echo(f"Moved comments to {file}:{len(lines) - lineno_from_end}") return "\n".join(lines) @@ -128,7 +129,7 @@ def find_element_constructor_usages( node.args.insert(1, maybe_attr_dict_node) else: continue - elif len(node.args) >= 2: + elif len(node.args) >= 2: # noqa: PLR2004 maybe_attr_dict_node = node.args[1] elif hasattr(html, name): if len(node.args) == 0: @@ -179,7 +180,7 @@ def _find_comments(lines: list[str]) -> list[str]: def _walk_with_parent( node: ast.AST, parents: tuple[ast.AST, ...] = () ) -> Iterator[tuple[tuple[ast.AST, ...], ast.AST]]: - parents = (node,) + parents + parents = (node, *parents) for child in ast.iter_child_nodes(node): yield parents, child yield from _walk_with_parent(child, parents) diff --git a/src/reactpy/_console/rewrite_camel_case_props.py b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py similarity index 96% rename from src/reactpy/_console/rewrite_camel_case_props.py rename to src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py index b67c210ab..e5d1860c2 100644 --- a/src/reactpy/_console/rewrite_camel_case_props.py +++ b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py @@ -16,7 +16,6 @@ rewrite_changed_nodes, ) - CAMEL_CASE_SUB_PATTERN = re.compile(r"(? None: """Rewrite camelCase props to snake_case""" - if sys.version_info < (3, 9): # pragma: no cover - raise RuntimeError("This command requires Python>=3.9") + if sys.version_info < (3, 9): # nocov + msg = "This command requires Python>=3.9" + raise RuntimeError(msg) for p in map(Path, paths): for f in [p] if p.is_file() else p.rglob("*.py"): diff --git a/src/reactpy/_console/rewrite_keys.py b/src/py/reactpy/reactpy/_console/rewrite_keys.py similarity index 96% rename from src/reactpy/_console/rewrite_keys.py rename to src/py/reactpy/reactpy/_console/rewrite_keys.py index 7ff672e1e..cbd90351a 100644 --- a/src/reactpy/_console/rewrite_keys.py +++ b/src/py/reactpy/reactpy/_console/rewrite_keys.py @@ -45,8 +45,9 @@ def rewrite_keys(paths: list[str]) -> None: just above its changes. As such it requires manual intervention to put those comments back in their original location. """ - if sys.version_info < (3, 9): # pragma: no cover - raise RuntimeError("This command requires Python>=3.9") + if sys.version_info < (3, 9): # nocov + msg = "This command requires Python>=3.9" + raise RuntimeError(msg) for p in map(Path, paths): for f in [p] if p.is_file() else p.rglob("*.py"): diff --git a/src/reactpy/_option.py b/src/py/reactpy/reactpy/_option.py similarity index 91% rename from src/reactpy/_option.py rename to src/py/reactpy/reactpy/_option.py index b86b7c0cc..1421f33a3 100644 --- a/src/reactpy/_option.py +++ b/src/py/reactpy/reactpy/_option.py @@ -6,7 +6,6 @@ from reactpy._warnings import warn - _O = TypeVar("_O") logger = getLogger(__name__) @@ -63,12 +62,12 @@ def current(self) -> _O: @current.setter def current(self, new: _O) -> None: self.set_current(new) - return None def subscribe(self, handler: Callable[[_O], None]) -> Callable[[_O], None]: """Register a callback that will be triggered when this option changes""" if not self.mutable: - raise TypeError("Immutable options cannot be subscribed to.") + msg = "Immutable options cannot be subscribed to." + raise TypeError(msg) self._subscribers.append(handler) handler(self.current) return handler @@ -83,7 +82,8 @@ def set_current(self, new: Any) -> None: Raises a ``TypeError`` if this option is not :attr:`Option.mutable`. """ if not self._mutable: - raise TypeError(f"{self} cannot be modified after initial load") + msg = f"{self} cannot be modified after initial load" + raise TypeError(msg) old = self.current new = self._current = self._validator(new) logger.debug(f"{self._name}={self._current}") @@ -107,7 +107,8 @@ def reload(self) -> None: def unset(self) -> None: """Remove the current value, the default will be used until it is set again.""" if not self._mutable: - raise TypeError(f"{self} cannot be modified after initial load") + msg = f"{self} cannot be modified after initial load" + raise TypeError(msg) old = self.current delattr(self, "_current") if self.current != old: @@ -118,7 +119,7 @@ def __repr__(self) -> str: return f"Option({self._name}={self.current!r})" -class DeprecatedOption(Option[_O]): # pragma: no cover +class DeprecatedOption(Option[_O]): # nocov def __init__(self, message: str, *args: Any, **kwargs: Any) -> None: self._deprecation_message = message super().__init__(*args, **kwargs) diff --git a/src/reactpy/_warnings.py b/src/py/reactpy/reactpy/_warnings.py similarity index 88% rename from src/reactpy/_warnings.py rename to src/py/reactpy/reactpy/_warnings.py index 573a5ab70..678b5f868 100644 --- a/src/reactpy/_warnings.py +++ b/src/py/reactpy/reactpy/_warnings.py @@ -1,7 +1,8 @@ +from collections.abc import Iterator from functools import wraps from inspect import currentframe from types import FrameType -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any from warnings import warn as _warn @@ -12,7 +13,7 @@ def warn(*args: Any, **kwargs: Any) -> Any: if TYPE_CHECKING: - warn = _warn + warn = _warn # noqa F811 def _frame_depth_in_module() -> int: diff --git a/src/reactpy/backend/__init__.py b/src/py/reactpy/reactpy/backend/__init__.py similarity index 100% rename from src/reactpy/backend/__init__.py rename to src/py/reactpy/reactpy/backend/__init__.py diff --git a/src/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py similarity index 91% rename from src/reactpy/backend/_common.py rename to src/py/reactpy/reactpy/backend/_common.py index 41bff8512..c2e9ee583 100644 --- a/src/reactpy/backend/_common.py +++ b/src/py/reactpy/reactpy/backend/_common.py @@ -2,9 +2,10 @@ import asyncio import os +from collections.abc import Awaitable, Sequence from dataclasses import dataclass from pathlib import Path, PurePosixPath -from typing import Any, Awaitable, Sequence, cast +from typing import Any, cast import uvicorn from asgiref.typing import ASGIApplication @@ -15,13 +16,12 @@ from reactpy.core.types import VdomDict from reactpy.utils import vdom_to_html - PATH_PREFIX = PurePosixPath("/_reactpy") MODULES_PATH = PATH_PREFIX / "modules" ASSETS_PATH = PATH_PREFIX / "assets" STREAM_PATH = PATH_PREFIX / "stream" -CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_client" +CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_static" / "app" / "dist" async def serve_development_asgi( @@ -53,7 +53,7 @@ async def serve_development_asgi( # Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's # order of operations. So we need to make sure `shutdown()` always has an initialized # list of `self.servers` to use. - if not hasattr(server, "servers"): # pragma: no cover + if not hasattr(server, "servers"): # nocov server.servers = [] await asyncio.wait_for(server.shutdown(), timeout=3) @@ -87,7 +87,8 @@ def traversal_safe_path(root: str | Path, *unsafe: str | Path) -> Path: if os.path.commonprefix([root, path]) != root: # If the common prefix is not root directory we resolved outside the root dir - raise ValueError("Unsafe path") + msg = "Unsafe path" + raise ValueError(msg) return Path(path) @@ -136,4 +137,5 @@ class CommonOptions: def __post_init__(self) -> None: if self.url_prefix and not self.url_prefix.startswith("/"): - raise ValueError("Expected 'url_prefix' to start with '/'") + msg = "Expected 'url_prefix' to start with '/'" + raise ValueError(msg) diff --git a/src/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py similarity index 71% rename from src/reactpy/backend/default.py rename to src/py/reactpy/reactpy/backend/default.py index b24bfc314..eb27ca93b 100644 --- a/src/reactpy/backend/default.py +++ b/src/py/reactpy/reactpy/backend/default.py @@ -5,12 +5,10 @@ from sys import exc_info from typing import Any, NoReturn +from reactpy.backend.types import BackendImplementation +from reactpy.backend.utils import all_implementations from reactpy.types import RootComponentConstructor -from .types import BackendImplementation -from .utils import all_implementations - - logger = getLogger(__name__) @@ -18,8 +16,9 @@ def configure( app: Any, component: RootComponentConstructor, options: None = None ) -> None: """Configure the given app instance to display the given component""" - if options is not None: # pragma: no cover - raise ValueError("Default implementation cannot be configured with options") + if options is not None: # nocov + msg = "Default implementation cannot be configured with options" + raise ValueError(msg) return _default_implementation().configure(app, component) @@ -28,9 +27,10 @@ def create_development_app() -> Any: return _default_implementation().create_development_app() -def Options(*args: Any, **kwargs: Any) -> NoReturn: +def Options(*args: Any, **kwargs: Any) -> NoReturn: # nocov """Create configuration options""" - raise ValueError("Default implementation has no options.") # pragma: no cover + msg = "Default implementation has no options." + raise ValueError(msg) async def serve_development_app( @@ -50,16 +50,17 @@ async def serve_development_app( def _default_implementation() -> BackendImplementation[Any]: """Get the first available server implementation""" - global _DEFAULT_IMPLEMENTATION + global _DEFAULT_IMPLEMENTATION # noqa PLW0603 if _DEFAULT_IMPLEMENTATION is not None: return _DEFAULT_IMPLEMENTATION try: implementation = next(all_implementations()) - except StopIteration: # pragma: no cover + except StopIteration: # nocov logger.debug("Backend implementation import failed", exc_info=exc_info()) - raise RuntimeError("No built-in server implementation installed.") + msg = "No built-in server implementation installed." + raise RuntimeError(msg) from None else: _DEFAULT_IMPLEMENTATION = implementation return implementation diff --git a/src/reactpy/backend/fastapi.py b/src/py/reactpy/reactpy/backend/fastapi.py similarity index 95% rename from src/reactpy/backend/fastapi.py rename to src/py/reactpy/reactpy/backend/fastapi.py index c3cd038d8..575fce1fe 100644 --- a/src/reactpy/backend/fastapi.py +++ b/src/py/reactpy/reactpy/backend/fastapi.py @@ -2,8 +2,7 @@ from fastapi import FastAPI -from . import starlette - +from reactpy.backend import starlette serve_development_app = starlette.serve_development_app """Alias for :func:`reactpy.backend.starlette.serve_development_app`""" diff --git a/src/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py similarity index 90% rename from src/reactpy/backend/flask.py rename to src/py/reactpy/reactpy/backend/flask.py index 19dfbe1c3..e28ed7652 100644 --- a/src/reactpy/backend/flask.py +++ b/src/py/reactpy/reactpy/backend/flask.py @@ -9,7 +9,7 @@ from queue import Queue as ThreadQueue from threading import Event as ThreadEvent from threading import Thread -from typing import Any, Callable, NamedTuple, NoReturn, Optional, cast +from typing import Any, Callable, NamedTuple, NoReturn, cast from flask import ( Blueprint, @@ -42,7 +42,6 @@ from reactpy.core.types import ComponentType, RootComponentConstructor from reactpy.utils import Ref - logger = logging.getLogger(__name__) @@ -112,8 +111,9 @@ def run_server() -> None: # the thread should eventually join thread.join(timeout=3) # just double check it happened - if thread.is_alive(): # pragma: no cover - raise RuntimeError("Failed to shutdown server.") + if thread.is_alive(): # nocov + msg = "Failed to shutdown server." + raise RuntimeError(msg) def use_websocket() -> WebSocket: @@ -129,11 +129,9 @@ def use_request() -> Request: def use_connection() -> Connection[_FlaskCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() - if not isinstance(conn.carrier, _FlaskCarrier): # pragma: no cover - raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Flask server?" - ) + if not isinstance(conn.carrier, _FlaskCarrier): # nocov + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" + raise TypeError(msg) return conn @@ -154,7 +152,7 @@ def _setup_common_routes( options: Options, ) -> None: cors_options = options.cors - if cors_options: # pragma: no cover + if cors_options: # nocov cors_params = cors_options if isinstance(cors_options, dict) else {} CORS(api_blueprint, **cors_params) @@ -204,20 +202,20 @@ def _dispatch_in_thread( path: str, component: ComponentType, send: Callable[[Any], None], - recv: Callable[[], Optional[Any]], + recv: Callable[[], Any | None], ) -> NoReturn: dispatch_thread_info_created = ThreadEvent() - dispatch_thread_info_ref: reactpy.Ref[ - Optional[_DispatcherThreadInfo] - ] = reactpy.Ref(None) + dispatch_thread_info_ref: reactpy.Ref[_DispatcherThreadInfo | None] = reactpy.Ref( + None + ) @copy_current_request_context def run_dispatcher() -> None: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - thread_send_queue: "ThreadQueue[Any]" = ThreadQueue() - async_recv_queue: "AsyncQueue[Any]" = AsyncQueue() + thread_send_queue: ThreadQueue[Any] = ThreadQueue() + async_recv_queue: AsyncQueue[Any] = AsyncQueue() async def send_coro(value: Any) -> None: thread_send_queue.put(value) @@ -258,7 +256,9 @@ async def main() -> None: dispatch_thread_info_created.wait() dispatch_thread_info = cast(_DispatcherThreadInfo, dispatch_thread_info_ref.current) - assert dispatch_thread_info is not None + + if dispatch_thread_info is None: + raise RuntimeError("Failed to create dispatcher thread") # nocov stop = ThreadEvent() @@ -274,7 +274,7 @@ def run_send() -> None: dispatch_thread_info.dispatch_loop.call_soon_threadsafe( dispatch_thread_info.async_recv_queue.put_nowait, value ) - finally: # pragma: no cover + finally: # nocov dispatch_thread_info.dispatch_loop.call_soon_threadsafe( dispatch_thread_info.dispatch_future.cancel ) diff --git a/src/reactpy/backend/hooks.py b/src/py/reactpy/reactpy/backend/hooks.py similarity index 75% rename from src/reactpy/backend/hooks.py rename to src/py/reactpy/reactpy/backend/hooks.py index 981461316..19ad114ed 100644 --- a/src/reactpy/backend/hooks.py +++ b/src/py/reactpy/reactpy/backend/hooks.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import Any, MutableMapping +from collections.abc import MutableMapping +from typing import Any +from reactpy.backend.types import Connection, Location from reactpy.core.hooks import Context, create_context, use_context -from .types import Connection, Location - - # backend implementations should establish this context at the root of an app ConnectionContext: Context[Connection[Any] | None] = create_context(None) @@ -14,8 +13,9 @@ def use_connection() -> Connection[Any]: """Get the current :class:`~reactpy.backend.types.Connection`.""" conn = use_context(ConnectionContext) - if conn is None: - raise RuntimeError("No backend established a connection.") # pragma: no cover + if conn is None: # nocov + msg = "No backend established a connection." + raise RuntimeError(msg) return conn diff --git a/src/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py similarity index 92% rename from src/reactpy/backend/sanic.py rename to src/py/reactpy/reactpy/backend/sanic.py index 91d20b838..53dd0ce68 100644 --- a/src/reactpy/backend/sanic.py +++ b/src/py/reactpy/reactpy/backend/sanic.py @@ -4,7 +4,7 @@ import json import logging from dataclasses import dataclass -from typing import Any, Tuple +from typing import Any from urllib import parse as urllib_parse from uuid import uuid4 @@ -13,12 +13,7 @@ from sanic.server.websockets.connection import WebSocketConnection from sanic_cors import CORS -from reactpy.backend.types import Connection, Location -from reactpy.core.layout import Layout -from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout -from reactpy.core.types import RootComponentConstructor - -from ._common import ( +from reactpy.backend._common import ( ASSETS_PATH, MODULES_PATH, PATH_PREFIX, @@ -29,9 +24,12 @@ safe_web_modules_dir_path, serve_development_asgi, ) -from .hooks import ConnectionContext -from .hooks import use_connection as _use_connection - +from reactpy.backend.hooks import ConnectionContext +from reactpy.backend.hooks import use_connection as _use_connection +from reactpy.backend.types import Connection, Location +from reactpy.core.layout import Layout +from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout +from reactpy.core.types import RootComponentConstructor logger = logging.getLogger(__name__) @@ -82,11 +80,9 @@ def use_websocket() -> WebSocketConnection: def use_connection() -> Connection[_SanicCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() - if not isinstance(conn.carrier, _SanicCarrier): # pragma: no cover - raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Sanic server?" - ) + if not isinstance(conn.carrier, _SanicCarrier): # nocov + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Sanic server?" + raise TypeError(msg) return conn @@ -107,7 +103,7 @@ def _setup_common_routes( options: Options, ) -> None: cors_options = options.cors - if cors_options: # pragma: no cover + if cors_options: # nocov cors_params = cors_options if isinstance(cors_options, dict) else {} CORS(api_blueprint, **cors_params) @@ -163,7 +159,7 @@ async def model_stream( ) -> None: asgi_app = getattr(request.app, "_asgi_app", None) scope = asgi_app.transport.scope if asgi_app else {} - if not scope: # pragma: no cover + if not scope: # nocov logger.warning("No scope. Sanic may not be running with an ASGI server") send, recv = _make_send_recv_callbacks(socket) @@ -203,7 +199,7 @@ async def model_stream( def _make_send_recv_callbacks( socket: WebSocketConnection, -) -> Tuple[SendCoroutine, RecvCoroutine]: +) -> tuple[SendCoroutine, RecvCoroutine]: async def sock_send(value: Any) -> None: await socket.send(json.dumps(value)) diff --git a/src/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py similarity index 91% rename from src/reactpy/backend/starlette.py rename to src/py/reactpy/reactpy/backend/starlette.py index 4ab16e91b..658fccfbd 100644 --- a/src/reactpy/backend/starlette.py +++ b/src/py/reactpy/reactpy/backend/starlette.py @@ -3,8 +3,9 @@ import asyncio import json import logging +from collections.abc import Awaitable from dataclasses import dataclass -from typing import Any, Awaitable, Callable, Tuple +from typing import Any, Callable from starlette.applications import Starlette from starlette.middleware.cors import CORSMiddleware @@ -13,14 +14,7 @@ from starlette.staticfiles import StaticFiles from starlette.websockets import WebSocket, WebSocketDisconnect -from reactpy.backend.hooks import ConnectionContext -from reactpy.backend.types import Connection, Location -from reactpy.config import REACTPY_WEB_MODULES_DIR -from reactpy.core.layout import Layout -from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout -from reactpy.core.types import RootComponentConstructor - -from ._common import ( +from reactpy.backend._common import ( ASSETS_PATH, CLIENT_BUILD_DIR, MODULES_PATH, @@ -29,9 +23,13 @@ read_client_index_html, serve_development_asgi, ) -from .hooks import ConnectionContext -from .hooks import use_connection as _use_connection - +from reactpy.backend.hooks import ConnectionContext +from reactpy.backend.hooks import use_connection as _use_connection +from reactpy.backend.types import Connection, Location +from reactpy.config import REACTPY_WEB_MODULES_DIR +from reactpy.core.layout import Layout +from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout +from reactpy.core.types import RootComponentConstructor logger = logging.getLogger(__name__) @@ -78,11 +76,9 @@ def use_websocket() -> WebSocket: def use_connection() -> Connection[WebSocket]: conn = _use_connection() - if not isinstance(conn.carrier, WebSocket): # pragma: no cover - raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Flask server?" - ) + if not isinstance(conn.carrier, WebSocket): # nocov + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" + raise TypeError(msg) return conn @@ -99,7 +95,7 @@ class Options(CommonOptions): def _setup_common_routes(options: Options, app: Starlette) -> None: cors_options = options.cors - if cors_options: # pragma: no cover + if cors_options: # nocov cors_params = ( cors_options if isinstance(cors_options, dict) else {"allow_origins": ["*"]} ) @@ -166,7 +162,7 @@ async def model_stream(socket: WebSocket) -> None: def _make_send_recv_callbacks( socket: WebSocket, -) -> Tuple[SendCoroutine, RecvCoroutine]: +) -> tuple[SendCoroutine, RecvCoroutine]: async def sock_send(value: Any) -> None: await socket.send_text(json.dumps(value)) diff --git a/src/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py similarity index 93% rename from src/reactpy/backend/tornado.py rename to src/py/reactpy/reactpy/backend/tornado.py index b79f5fa0e..5ec877532 100644 --- a/src/reactpy/backend/tornado.py +++ b/src/py/reactpy/reactpy/backend/tornado.py @@ -16,13 +16,7 @@ from tornado.wsgi import WSGIContainer from typing_extensions import TypeAlias -from reactpy.backend.types import Connection, Location -from reactpy.config import REACTPY_WEB_MODULES_DIR -from reactpy.core.layout import Layout -from reactpy.core.serve import serve_layout -from reactpy.core.types import ComponentConstructor - -from ._common import ( +from reactpy.backend._common import ( ASSETS_PATH, CLIENT_BUILD_DIR, MODULES_PATH, @@ -30,9 +24,13 @@ CommonOptions, read_client_index_html, ) -from .hooks import ConnectionContext -from .hooks import use_connection as _use_connection - +from reactpy.backend.hooks import ConnectionContext +from reactpy.backend.hooks import use_connection as _use_connection +from reactpy.backend.types import Connection, Location +from reactpy.config import REACTPY_WEB_MODULES_DIR +from reactpy.core.layout import Layout +from reactpy.core.serve import serve_layout +from reactpy.core.types import ComponentConstructor Options = CommonOptions """Render server config for :func:`reactpy.backend.tornado.configure`""" @@ -100,11 +98,9 @@ def use_request() -> HTTPServerRequest: def use_connection() -> Connection[HTTPServerRequest]: conn = _use_connection() - if not isinstance(conn.carrier, HTTPServerRequest): # pragma: no cover - raise TypeError( - f"Connection has unexpected carrier {conn.carrier}. " - "Are you running with a Flask server?" - ) + if not isinstance(conn.carrier, HTTPServerRequest): # nocov + msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?" + raise TypeError(msg) return conn @@ -135,7 +131,7 @@ def _add_handler( app: Application, options: Options, handlers: _RouteHandlerSpecs ) -> None: prefixed_handlers: list[Any] = [ - (urljoin(options.url_prefix, route_pattern),) + tuple(handler_info) + (urljoin(options.url_prefix, route_pattern), *tuple(handler_info)) for route_pattern, *handler_info in handlers ] app.add_handlers(r".*", prefixed_handlers) @@ -181,7 +177,7 @@ def initialize( self._url_prefix = url_prefix async def open(self, path: str = "", *args: Any, **kwargs: Any) -> None: - message_queue: "AsyncQueue[str]" = AsyncQueue() + message_queue: AsyncQueue[str] = AsyncQueue() async def send(value: Any) -> None: await self.write_message(json.dumps(value)) diff --git a/src/reactpy/backend/types.py b/src/py/reactpy/reactpy/backend/types.py similarity index 93% rename from src/reactpy/backend/types.py rename to src/py/reactpy/reactpy/backend/types.py index fb19aa5d0..fbc4addc0 100644 --- a/src/reactpy/backend/types.py +++ b/src/py/reactpy/reactpy/backend/types.py @@ -1,14 +1,12 @@ from __future__ import annotations import asyncio +from collections.abc import MutableMapping from dataclasses import dataclass -from typing import Any, Callable, Generic, MutableMapping, TypeVar - -from typing_extensions import Protocol, runtime_checkable +from typing import Any, Callable, Generic, Protocol, TypeVar, runtime_checkable from reactpy.core.types import RootComponentConstructor - _App = TypeVar("_App") diff --git a/src/reactpy/backend/utils.py b/src/py/reactpy/reactpy/backend/utils.py similarity index 86% rename from src/reactpy/backend/utils.py rename to src/py/reactpy/reactpy/backend/utils.py index b7576cfb0..3d9be13a4 100644 --- a/src/reactpy/backend/utils.py +++ b/src/py/reactpy/reactpy/backend/utils.py @@ -3,15 +3,14 @@ import asyncio import logging import socket +from collections.abc import Iterator from contextlib import closing from importlib import import_module -from typing import Any, Iterator +from typing import Any +from reactpy.backend.types import BackendImplementation from reactpy.types import RootComponentConstructor -from .types import BackendImplementation - - logger = logging.getLogger(__name__) SUPPORTED_PACKAGES = ( @@ -70,9 +69,8 @@ def find_available_port( pass else: return port - raise RuntimeError( - f"Host {host!r} has no available port in range {port_max}-{port_max}" - ) + msg = f"Host {host!r} has no available port in range {port_max}-{port_max}" + raise RuntimeError(msg) def all_implementations() -> Iterator[BackendImplementation[Any]]: @@ -81,12 +79,13 @@ def all_implementations() -> Iterator[BackendImplementation[Any]]: try: relative_import_name = f"{__name__.rsplit('.', 1)[0]}.{name}" module = import_module(relative_import_name) - except ImportError: # pragma: no cover + except ImportError: # nocov logger.debug(f"Failed to import {name!r}", exc_info=True) continue - if not isinstance(module, BackendImplementation): # pragma: no cover - raise TypeError(f"{module.__name__!r} is an invalid implementation") + if not isinstance(module, BackendImplementation): # nocov + msg = f"{module.__name__!r} is an invalid implementation" + raise TypeError(msg) yield module diff --git a/src/reactpy/config.py b/src/py/reactpy/reactpy/config.py similarity index 97% rename from src/reactpy/config.py rename to src/py/reactpy/reactpy/config.py index 8f26271c4..ace93b6d2 100644 --- a/src/reactpy/config.py +++ b/src/py/reactpy/reactpy/config.py @@ -6,8 +6,7 @@ from pathlib import Path from tempfile import TemporaryDirectory -from ._option import Option as _Option - +from reactpy._option import Option as _Option REACTPY_DEBUG_MODE = _Option( "REACTPY_DEBUG_MODE", diff --git a/src/reactpy/core/__init__.py b/src/py/reactpy/reactpy/core/__init__.py similarity index 100% rename from src/reactpy/core/__init__.py rename to src/py/reactpy/reactpy/core/__init__.py diff --git a/src/reactpy/core/_f_back.py b/src/py/reactpy/reactpy/core/_f_back.py similarity index 73% rename from src/reactpy/core/_f_back.py rename to src/py/reactpy/reactpy/core/_f_back.py index 81e66a4f1..fe1a6c10c 100644 --- a/src/reactpy/core/_f_back.py +++ b/src/py/reactpy/reactpy/core/_f_back.py @@ -7,9 +7,10 @@ def f_module_name(index: int = 0) -> str: frame = f_back(index + 1) if frame is None: - return "" # pragma: no cover + return "" # nocov name = frame.f_globals.get("__name__", "") - assert isinstance(name, str), "Expected module name to be a string" + if not isinstance(name, str): + raise TypeError("Expected module name to be a string") # nocov return name @@ -20,4 +21,4 @@ def f_back(index: int = 0) -> FrameType | None: return frame frame = frame.f_back index -= 1 - return None # pragma: no cover + return None # nocov diff --git a/src/reactpy/core/_thread_local.py b/src/py/reactpy/reactpy/core/_thread_local.py similarity index 99% rename from src/reactpy/core/_thread_local.py rename to src/py/reactpy/reactpy/core/_thread_local.py index 80a42d069..b3d6a14b0 100644 --- a/src/reactpy/core/_thread_local.py +++ b/src/py/reactpy/reactpy/core/_thread_local.py @@ -2,7 +2,6 @@ from typing import Callable, Generic, TypeVar from weakref import WeakKeyDictionary - _StateType = TypeVar("_StateType") diff --git a/src/reactpy/core/component.py b/src/py/reactpy/reactpy/core/component.py similarity index 80% rename from src/reactpy/core/component.py rename to src/py/reactpy/reactpy/core/component.py index 79731ad4e..f825aac71 100644 --- a/src/reactpy/core/component.py +++ b/src/py/reactpy/reactpy/core/component.py @@ -2,9 +2,9 @@ import inspect from functools import wraps -from typing import Any, Callable, Dict, Optional, Tuple +from typing import Any, Callable -from .types import ComponentType, VdomDict +from reactpy.core.types import ComponentType, VdomDict def component( @@ -21,12 +21,11 @@ def component( inspect.Parameter.KEYWORD_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD, ): - raise TypeError( - f"Component render function {function} uses reserved parameter 'key'" - ) + msg = f"Component render function {function} uses reserved parameter 'key'" + raise TypeError(msg) @wraps(function) - def constructor(*args: Any, key: Optional[Any] = None, **kwargs: Any) -> Component: + def constructor(*args: Any, key: Any | None = None, **kwargs: Any) -> Component: return Component(function, key, args, kwargs, sig) return constructor @@ -40,9 +39,9 @@ class Component: def __init__( self, function: Callable[..., ComponentType | VdomDict | str | None], - key: Optional[Any], - args: Tuple[Any, ...], - kwargs: Dict[str, Any], + key: Any | None, + args: tuple[Any, ...], + kwargs: dict[str, Any], sig: inspect.Signature, ) -> None: self.key = key diff --git a/src/reactpy/core/events.py b/src/py/reactpy/reactpy/core/events.py similarity index 93% rename from src/reactpy/core/events.py rename to src/py/reactpy/reactpy/core/events.py index 4cad9fd7f..51fe8d0d3 100644 --- a/src/reactpy/core/events.py +++ b/src/py/reactpy/reactpy/core/events.py @@ -1,10 +1,10 @@ from __future__ import annotations import asyncio -from typing import Any, Callable, Optional, Sequence, overload +from collections.abc import Sequence +from typing import Any, Callable, Literal, overload from anyio import create_task_group -from typing_extensions import Literal from reactpy.core.types import EventHandlerFunc, EventHandlerType @@ -104,7 +104,7 @@ def __init__( function: EventHandlerFunc, stop_propagation: bool = False, prevent_default: bool = False, - target: Optional[str] = None, + target: str | None = None, ) -> None: self.function = to_event_handler_function(function, positional_args=False) self.prevent_default = prevent_default @@ -174,7 +174,8 @@ def merge_event_handlers( :attr:`~reactpy.core.proto.EventHandlerType.prevent_default` attributes. """ if not event_handlers: - raise ValueError("No event handlers to merge") + msg = "No event handlers to merge" + raise ValueError(msg) elif len(event_handlers) == 1: return event_handlers[0] @@ -190,10 +191,8 @@ def merge_event_handlers( or handler.prevent_default != prevent_default or handler.target != target ): - raise ValueError( - "Cannot merge handlers - " - "'stop_propagation', 'prevent_default' or 'target' mistmatch." - ) + msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mistmatch." + raise ValueError(msg) return EventHandler( merge_event_handler_funcs([h.function for h in event_handlers]), @@ -208,7 +207,8 @@ def merge_event_handler_funcs( ) -> EventHandlerFunc: """Make one event handler function from many""" if not functions: - raise ValueError("No event handler functions to merge") + msg = "No event handler functions to merge" + raise ValueError(msg) elif len(functions) == 1: return functions[0] diff --git a/src/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py similarity index 96% rename from src/reactpy/core/hooks.py rename to src/py/reactpy/reactpy/core/hooks.py index c40a6869f..9b025c0ca 100644 --- a/src/reactpy/core/hooks.py +++ b/src/py/reactpy/reactpy/core/hooks.py @@ -1,30 +1,28 @@ from __future__ import annotations import asyncio +from collections.abc import Awaitable, Sequence from logging import getLogger from types import FunctionType from typing import ( TYPE_CHECKING, Any, - Awaitable, Callable, Generic, NewType, - Sequence, + Protocol, TypeVar, cast, overload, ) -from typing_extensions import Protocol, TypeAlias +from typing_extensions import TypeAlias from reactpy.config import REACTPY_DEBUG_MODE +from reactpy.core._thread_local import ThreadLocal +from reactpy.core.types import ComponentType, Key, State, VdomDict from reactpy.utils import Ref -from ._thread_local import ThreadLocal -from .types import ComponentType, Key, State, VdomDict - - if not TYPE_CHECKING: # make flake8 think that this variable exists ellipsis = type(...) @@ -167,8 +165,6 @@ def effect() -> None: if clean is not None: hook.add_effect(COMPONENT_WILL_UNMOUNT_EFFECT, clean) - return None - return memoize(lambda: hook.add_effect(LAYOUT_DID_RENDER_EFFECT, effect)) if function is not None: @@ -250,13 +246,13 @@ def use_context(context: Context[_Type]) -> _Type: provider = hook.get_context_provider(context) if provider is None: - # force type checker to realize this is just a normal function - assert isinstance(context, FunctionType), f"{context} is not a Context" - # __kwdefault__ can be None if no kwarg only parameters exist - assert context.__kwdefaults__ is not None, f"{context} has no 'value' kwarg" - # lastly check that 'value' kwarg exists - assert "value" in context.__kwdefaults__, f"{context} has no 'value' kwarg" - # then we can safely access the context's default value + # same assertions but with normal exceptions + if not isinstance(context, FunctionType): + raise TypeError(f"{context} is not a Context") # nocov + if context.__kwdefaults__ is None: + raise TypeError(f"{context} has no 'value' kwarg") # nocov + if "value" not in context.__kwdefaults__: + raise TypeError(f"{context} has no 'value' kwarg") # nocov return cast(_Type, context.__kwdefaults__["value"]) return provider._value @@ -459,7 +455,7 @@ class _Memo(Generic[_Type]): def empty(self) -> bool: try: - self.value + self.value # noqa: B018 except AttributeError: return True else: @@ -627,7 +623,6 @@ def schedule_render(self) -> None: self._schedule_render_later = True else: self._schedule_render() - return None def use_state(self, function: Callable[[], _Type]) -> _Type: if not self._rendered_atleast_once: @@ -713,8 +708,8 @@ def set_current(self) -> None: def unset_current(self) -> None: """Unset this hook as the active hook in this thread""" - # this assertion should never fail - primarilly useful for debug - assert _hook_stack.get().pop() is self + if _hook_stack.get().pop() is not self: + raise RuntimeError("Hook stack is in an invalid state") # nocov def _schedule_render(self) -> None: try: diff --git a/src/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py similarity index 92% rename from src/reactpy/core/layout.py rename to src/py/reactpy/reactpy/core/layout.py index 2b785815e..368e79c7d 100644 --- a/src/reactpy/core/layout.py +++ b/src/py/reactpy/reactpy/core/layout.py @@ -3,16 +3,15 @@ import abc import asyncio from collections import Counter +from collections.abc import Iterator from contextlib import ExitStack from logging import getLogger from typing import ( Any, Callable, Generic, - Iterator, NamedTuple, NewType, - Optional, TypeVar, cast, ) @@ -20,10 +19,8 @@ from weakref import ref as weakref from reactpy.config import REACTPY_CHECK_VDOM_SPEC, REACTPY_DEBUG_MODE -from reactpy.utils import Ref - -from .hooks import LifeCycleHook -from .types import ( +from reactpy.core.hooks import LifeCycleHook +from reactpy.core.types import ( ComponentType, EventHandlerDict, LayoutEventMessage, @@ -31,8 +28,8 @@ VdomDict, VdomJson, ) -from .vdom import validate_vdom_json - +from reactpy.core.vdom import validate_vdom_json +from reactpy.utils import Ref logger = getLogger(__name__) @@ -48,13 +45,14 @@ class Layout: "_model_states_by_life_cycle_state_id", ] - if not hasattr(abc.ABC, "__weakref__"): # pragma: no cover + if not hasattr(abc.ABC, "__weakref__"): # nocov __slots__.append("__weakref__") - def __init__(self, root: "ComponentType") -> None: + def __init__(self, root: ComponentType) -> None: super().__init__() if not isinstance(root, ComponentType): - raise TypeError(f"Expected a ComponentType, not {type(root)!r}.") + msg = f"Expected a ComponentType, not {type(root)!r}." + raise TypeError(msg) self.root = root async def __aenter__(self) -> Layout: @@ -82,8 +80,6 @@ async def __aexit__(self, *exc: Any) -> None: del self._root_life_cycle_state_id del self._model_states_by_life_cycle_state_id - return None - async def deliver(self, event: LayoutEventMessage) -> None: """Dispatch an event to the targeted handler""" # It is possible for an element in the frontend to produce an event @@ -138,7 +134,7 @@ def _create_layout_update(self, old_state: _ModelState) -> LayoutUpdateMessage: def _render_component( self, exit_stack: ExitStack, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, component: ComponentType, ) -> None: @@ -195,14 +191,15 @@ def _render_component( def _render_model( self, exit_stack: ExitStack, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, raw_model: Any, ) -> None: try: new_state.model.current = {"tagName": raw_model["tagName"]} - except Exception as e: # pragma: no cover - raise ValueError(f"Expected a VDOM element dict, not {raw_model}") from e + except Exception as e: # nocov + msg = f"Expected a VDOM element dict, not {raw_model}" + raise ValueError(msg) from e if "key" in raw_model: new_state.key = new_state.model.current["key"] = raw_model["key"] if "importSource" in raw_model: @@ -214,7 +211,7 @@ def _render_model( def _render_model_attributes( self, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, raw_model: dict[str, Any], ) -> None: @@ -278,7 +275,7 @@ def _render_model_event_handlers_without_old_state( def _render_model_children( self, exit_stack: ExitStack, - old_state: Optional[_ModelState], + old_state: _ModelState | None, new_state: _ModelState, raw_children: Any, ) -> None: @@ -301,9 +298,8 @@ def _render_model_children( if len(new_keys) != len(raw_children): key_counter = Counter(item[2] for item in child_type_key_tuples) duplicate_keys = [key for key, count in key_counter.items() if count > 1] - raise ValueError( - f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" - ) + msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" + raise ValueError(msg) old_keys = set(old_state.children_by_key).difference(new_keys) if old_keys: @@ -322,21 +318,20 @@ def _render_model_children( index, key, ) + elif old_child_state.is_component_state: + self._unmount_model_states([old_child_state]) + new_child_state = _make_element_model_state( + new_state, + index, + key, + ) + old_child_state = None else: - if old_child_state.is_component_state: - self._unmount_model_states([old_child_state]) - new_child_state = _make_element_model_state( - new_state, - index, - key, - ) - old_child_state = None - else: - new_child_state = _update_element_model_state( - old_child_state, - new_state, - index, - ) + new_child_state = _update_element_model_state( + old_child_state, + new_state, + index, + ) self._render_model(exit_stack, old_child_state, new_child_state, child) new_state.append_child(new_child_state.model.current) new_state.children_by_key[key] = new_child_state @@ -392,9 +387,8 @@ def _render_model_children_without_old_state( if len(new_keys) != len(raw_children): key_counter = Counter(item[2] for item in child_type_key_tuples) duplicate_keys = [key for key, count in key_counter.items() if count > 1] - raise ValueError( - f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" - ) + msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}" + raise ValueError(msg) new_state.model.current["children"] = [] for index, (child, child_type, key) in enumerate(child_type_key_tuples): @@ -467,7 +461,7 @@ def _make_component_model_state( def _copy_component_model_state(old_model_state: _ModelState) -> _ModelState: # use try/except here because not having a parent is rare (only the root state) try: - parent: Optional[_ModelState] = old_model_state.parent + parent: _ModelState | None = old_model_state.parent except AttributeError: parent = None @@ -555,14 +549,14 @@ class _ModelState: def __init__( self, - parent: Optional[_ModelState], + parent: _ModelState | None, index: int, key: Any, model: Ref[VdomJson], patch_path: str, children_by_key: dict[str, _ModelState], targets_by_event: dict[str, str], - life_cycle_state: Optional[_LifeCycleState] = None, + life_cycle_state: _LifeCycleState | None = None, ): self.index = index """The index of the element amongst its siblings""" @@ -600,13 +594,14 @@ def is_component_state(self) -> bool: @property def parent(self) -> _ModelState: parent = self._parent_ref() - assert parent is not None, "detached model state" + if parent is None: + raise RuntimeError("detached model state") # nocov return parent def append_child(self, child: Any) -> None: self.model.current["children"].append(child) - def __repr__(self) -> str: # pragma: no cover + def __repr__(self) -> str: # nocov return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })" @@ -665,7 +660,6 @@ def put(self, value: _Type) -> None: if value not in self._pending: self._pending.add(value) self._loop.call_soon_threadsafe(self._queue.put_nowait, value) - return None async def get(self) -> _Type: while True: diff --git a/src/reactpy/core/serve.py b/src/py/reactpy/reactpy/core/serve.py similarity index 85% rename from src/reactpy/core/serve.py rename to src/py/reactpy/reactpy/core/serve.py index 2802df9e2..61a7e4ce6 100644 --- a/src/reactpy/core/serve.py +++ b/src/py/reactpy/reactpy/core/serve.py @@ -1,14 +1,14 @@ from __future__ import annotations -from asyncio import create_task +from collections.abc import Awaitable from logging import getLogger -from typing import Awaitable, Callable +from typing import Callable from anyio import create_task_group +from anyio.abc import TaskGroup from reactpy.core.types import LayoutEventMessage, LayoutType, LayoutUpdateMessage - logger = getLogger(__name__) @@ -40,7 +40,7 @@ async def serve_layout( try: async with create_task_group() as task_group: task_group.start_soon(_single_outgoing_loop, layout, send) - task_group.start_soon(_single_incoming_loop, layout, recv) + task_group.start_soon(_single_incoming_loop, task_group, layout, recv) except Stop: logger.info(f"Stopped serving {layout}") @@ -53,9 +53,11 @@ async def _single_outgoing_loop( async def _single_incoming_loop( - layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], recv: RecvCoroutine + task_group: TaskGroup, + layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], + recv: RecvCoroutine, ) -> None: while True: # We need to fire and forget here so that we avoid waiting on the completion # of this event handler before receiving and running the next one. - create_task(layout.deliver(await recv())) + task_group.start_soon(layout.deliver, await recv()) diff --git a/src/reactpy/core/types.py b/src/py/reactpy/reactpy/core/types.py similarity index 90% rename from src/reactpy/core/types.py rename to src/py/reactpy/reactpy/core/types.py index e9ec39c1a..93f4fe36b 100644 --- a/src/reactpy/core/types.py +++ b/src/py/reactpy/reactpy/core/types.py @@ -2,22 +2,22 @@ import sys from collections import namedtuple -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from types import TracebackType from typing import ( TYPE_CHECKING, Any, Callable, Generic, - Mapping, + Literal, NamedTuple, - Type, + Protocol, TypeVar, overload, + runtime_checkable, ) -from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, runtime_checkable - +from typing_extensions import TypeAlias, TypedDict _Type = TypeVar("_Type") @@ -28,7 +28,7 @@ class State(NamedTuple, Generic[_Type]): value: _Type set_value: Callable[[_Type | Callable[[_Type], _Type]], None] -else: # pragma: no cover +else: # nocov State = namedtuple("State", ("value", "set_value")) @@ -81,7 +81,7 @@ async def __aenter__(self) -> LayoutType[_Render, _Event]: async def __aexit__( self, - exc_type: Type[Exception], + exc_type: type[Exception], exc_value: Exception, traceback: TracebackType, ) -> bool | None: @@ -109,12 +109,12 @@ class _VdomDictOptional(TypedDict, total=False): | Any ] attributes: VdomAttributes - eventHandlers: EventHandlerDict # noqa - importSource: ImportSourceDict # noqa + eventHandlers: EventHandlerDict + importSource: ImportSourceDict class _VdomDictRequired(TypedDict, total=True): - tagName: str # noqa + tagName: str class VdomDict(_VdomDictRequired, _VdomDictOptional): @@ -124,8 +124,8 @@ class VdomDict(_VdomDictRequired, _VdomDictOptional): class ImportSourceDict(TypedDict): source: str fallback: Any - sourceType: str # noqa - unmountBeforeUpdate: bool # noqa + sourceType: str + unmountBeforeUpdate: bool class _OptionalVdomJson(TypedDict, total=False): @@ -133,12 +133,12 @@ class _OptionalVdomJson(TypedDict, total=False): error: str children: list[Any] attributes: dict[str, Any] - eventHandlers: dict[str, _JsonEventTarget] # noqa - importSource: _JsonImportSource # noqa + eventHandlers: dict[str, _JsonEventTarget] + importSource: _JsonImportSource class _RequiredVdomJson(TypedDict, total=True): - tagName: str # noqa + tagName: str class VdomJson(_RequiredVdomJson, _OptionalVdomJson): @@ -147,8 +147,8 @@ class VdomJson(_RequiredVdomJson, _OptionalVdomJson): class _JsonEventTarget(TypedDict): target: str - preventDefault: bool # noqa - stopPropagation: bool # noqa + preventDefault: bool + stopPropagation: bool class _JsonImportSource(TypedDict): diff --git a/src/reactpy/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py similarity index 96% rename from src/reactpy/core/vdom.py rename to src/py/reactpy/reactpy/core/vdom.py index 9579560b9..c716fe347 100644 --- a/src/reactpy/core/vdom.py +++ b/src/py/reactpy/reactpy/core/vdom.py @@ -1,14 +1,15 @@ from __future__ import annotations import logging +from collections.abc import Mapping, Sequence from functools import wraps -from typing import Any, Mapping, Sequence, cast, overload +from typing import Any, Protocol, cast, overload from fastjsonschema import compile as compile_json_schema -from typing_extensions import Protocol from reactpy._warnings import warn from reactpy.config import REACTPY_DEBUG_MODE +from reactpy.core._f_back import f_module_name from reactpy.core.events import EventHandler, to_event_handler_function from reactpy.core.types import ( ComponentType, @@ -24,9 +25,6 @@ VdomJson, ) -from ._f_back import f_module_name - - logger = logging.getLogger() @@ -164,7 +162,7 @@ def vdom( (subject to change) specifies javascript that, when evaluated returns a React component. """ - if kwargs: # pragma: no cover + if kwargs: # nocov if "key" in kwargs: if attributes_and_children: maybe_attributes, *children = attributes_and_children @@ -188,7 +186,8 @@ def vdom( ) if kwargs: - raise ValueError(f"Extra keyword arguments {kwargs}") + msg = f"Extra keyword arguments {kwargs}" + raise ValueError(msg) model: VdomDict = {"tagName": tag} @@ -226,7 +225,8 @@ def make_vdom_constructor( def constructor(*attributes_and_children: Any, **kwargs: Any) -> VdomDict: model = vdom(tag, *attributes_and_children, **kwargs) if not allow_children and "children" in model: - raise TypeError(f"{tag!r} nodes cannot have children.") + msg = f"{tag!r} nodes cannot have children." + raise TypeError(msg) if import_source: model["importSource"] = import_source return model @@ -308,7 +308,7 @@ def separate_attributes_and_event_handlers( separated_event_handlers[k] = handler - return separated_attributes, {k: h for k, h in separated_event_handlers.items()} + return separated_attributes, dict(separated_event_handlers.items()) def _is_attributes(value: Any) -> bool: diff --git a/src/reactpy/future.py b/src/py/reactpy/reactpy/future.py similarity index 100% rename from src/reactpy/future.py rename to src/py/reactpy/reactpy/future.py diff --git a/src/reactpy/html.py b/src/py/reactpy/reactpy/html.py similarity index 95% rename from src/reactpy/html.py rename to src/py/reactpy/reactpy/html.py index 5c1d8b6ad..0387b3135 100644 --- a/src/reactpy/html.py +++ b/src/py/reactpy/reactpy/html.py @@ -158,7 +158,7 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence from reactpy.core.types import ( EventHandlerDict, @@ -169,7 +169,6 @@ ) from reactpy.core.vdom import custom_vdom_constructor, make_vdom_constructor - __all__ = ( "_", "a", @@ -293,7 +292,8 @@ def _fragment( ) -> VdomDict: """An HTML fragment - this element will not appear in the DOM""" if attributes or event_handlers: - raise TypeError("Fragments cannot have attributes besides 'key'") + msg = "Fragments cannot have attributes besides 'key'" + raise TypeError(msg) model: VdomDict = {"tagName": ""} if children: @@ -382,14 +382,14 @@ def _fragment( area = make_vdom_constructor("area", allow_children=False) audio = make_vdom_constructor("audio") img = make_vdom_constructor("img", allow_children=False) -map = make_vdom_constructor("map") +map = make_vdom_constructor("map") # noqa: A001 track = make_vdom_constructor("track") video = make_vdom_constructor("video") # Embedded content embed = make_vdom_constructor("embed", allow_children=False) iframe = make_vdom_constructor("iframe", allow_children=False) -object = make_vdom_constructor("object") +object = make_vdom_constructor("object") # noqa: A001 param = make_vdom_constructor("param") picture = make_vdom_constructor("picture") portal = make_vdom_constructor("portal", allow_children=False) @@ -473,13 +473,16 @@ def _script( model: VdomDict = {"tagName": "script"} if event_handlers: - raise ValueError("'script' elements do not support event handlers") + msg = "'script' elements do not support event handlers" + raise ValueError(msg) if children: if len(children) > 1: - raise ValueError("'script' nodes may have, at most, one child.") + msg = "'script' nodes may have, at most, one child." + raise ValueError(msg) elif not isinstance(children[0], str): - raise ValueError("The child of a 'script' must be a string.") + msg = "The child of a 'script' must be a string." + raise ValueError(msg) else: model["children"] = children if key is None: @@ -519,7 +522,7 @@ def _script( button = make_vdom_constructor("button") fieldset = make_vdom_constructor("fieldset") form = make_vdom_constructor("form") -input = make_vdom_constructor("input", allow_children=False) +input = make_vdom_constructor("input", allow_children=False) # noqa: A001 label = make_vdom_constructor("label") legend = make_vdom_constructor("legend") meter = make_vdom_constructor("meter") diff --git a/src/reactpy/logging.py b/src/py/reactpy/reactpy/logging.py similarity index 95% rename from src/reactpy/logging.py rename to src/py/reactpy/reactpy/logging.py index 1a07984a1..f10414cb6 100644 --- a/src/reactpy/logging.py +++ b/src/py/reactpy/reactpy/logging.py @@ -2,8 +2,7 @@ import sys from logging.config import dictConfig -from .config import REACTPY_DEBUG_MODE - +from reactpy.config import REACTPY_DEBUG_MODE dictConfig( { diff --git a/src/reactpy/py.typed b/src/py/reactpy/reactpy/py.typed similarity index 100% rename from src/reactpy/py.typed rename to src/py/reactpy/reactpy/py.typed diff --git a/src/reactpy/sample.py b/src/py/reactpy/reactpy/sample.py similarity index 81% rename from src/reactpy/sample.py rename to src/py/reactpy/reactpy/sample.py index 7905c1586..8509c773d 100644 --- a/src/reactpy/sample.py +++ b/src/py/reactpy/reactpy/sample.py @@ -1,8 +1,8 @@ from __future__ import annotations -from . import html -from .core.component import component -from .core.types import VdomDict +from reactpy import html +from reactpy.core.component import component +from reactpy.core.types import VdomDict @component diff --git a/src/reactpy/svg.py b/src/py/reactpy/reactpy/svg.py similarity index 97% rename from src/reactpy/svg.py rename to src/py/reactpy/reactpy/svg.py index ef9995468..ebfe58ee6 100644 --- a/src/reactpy/svg.py +++ b/src/py/reactpy/reactpy/svg.py @@ -1,6 +1,5 @@ from reactpy.core.vdom import make_vdom_constructor - __all__ = ( "a", "animate", @@ -107,7 +106,7 @@ fe_spot_light = make_vdom_constructor("feSpotLight", allow_children=False) fe_tile = make_vdom_constructor("feTile", allow_children=False) fe_turbulence = make_vdom_constructor("feTurbulence", allow_children=False) -filter = make_vdom_constructor("filter", allow_children=False) +filter = make_vdom_constructor("filter", allow_children=False) # noqa: A001 foreign_object = make_vdom_constructor("foreignObject", allow_children=False) g = make_vdom_constructor("g") hatch = make_vdom_constructor("hatch", allow_children=False) @@ -126,7 +125,7 @@ radial_gradient = make_vdom_constructor("radialGradient", allow_children=False) rect = make_vdom_constructor("rect", allow_children=False) script = make_vdom_constructor("script", allow_children=False) -set = make_vdom_constructor("set", allow_children=False) +set = make_vdom_constructor("set", allow_children=False) # noqa: A001 stop = make_vdom_constructor("stop", allow_children=False) style = make_vdom_constructor("style", allow_children=False) svg = make_vdom_constructor("svg") diff --git a/src/reactpy/testing/__init__.py b/src/py/reactpy/reactpy/testing/__init__.py similarity index 59% rename from src/reactpy/testing/__init__.py rename to src/py/reactpy/reactpy/testing/__init__.py index 1c9fcda1c..9f61cec57 100644 --- a/src/reactpy/testing/__init__.py +++ b/src/py/reactpy/reactpy/testing/__init__.py @@ -1,14 +1,18 @@ -from .backend import BackendFixture -from .common import HookCatcher, StaticEventHandler, clear_reactpy_web_modules_dir, poll -from .display import DisplayFixture -from .logs import ( +from reactpy.testing.backend import BackendFixture +from reactpy.testing.common import ( + HookCatcher, + StaticEventHandler, + clear_reactpy_web_modules_dir, + poll, +) +from reactpy.testing.display import DisplayFixture +from reactpy.testing.logs import ( LogAssertionError, assert_reactpy_did_log, assert_reactpy_did_not_log, capture_reactpy_logs, ) - __all__ = [ "assert_reactpy_did_not_log", "assert_reactpy_did_log", diff --git a/src/reactpy/testing/backend.py b/src/py/reactpy/reactpy/testing/backend.py similarity index 87% rename from src/reactpy/testing/backend.py rename to src/py/reactpy/reactpy/testing/backend.py index 5749e1fef..8e4042284 100644 --- a/src/reactpy/testing/backend.py +++ b/src/py/reactpy/reactpy/testing/backend.py @@ -4,7 +4,7 @@ import logging from contextlib import AsyncExitStack from types import TracebackType -from typing import Any, Callable, Optional, Tuple, Type, Union +from typing import Any, Callable from urllib.parse import urlencode, urlunparse from reactpy.backend import default as default_server @@ -14,10 +14,13 @@ from reactpy.core.component import component from reactpy.core.hooks import use_callback, use_effect, use_state from reactpy.core.types import ComponentConstructor +from reactpy.testing.logs import ( + LogAssertionError, + capture_reactpy_logs, + list_logged_exceptions, +) from reactpy.utils import Ref -from .logs import LogAssertionError, capture_reactpy_logs, list_logged_exceptions - class BackendFixture: """A test fixture for running a server and imperatively displaying views @@ -38,7 +41,7 @@ class BackendFixture: def __init__( self, host: str = "127.0.0.1", - port: Optional[int] = None, + port: int | None = None, app: Any | None = None, implementation: BackendImplementation[Any] | None = None, options: Any | None = None, @@ -53,10 +56,8 @@ def __init__( if app is not None: if implementation is None: - raise ValueError( - "If an application instance its corresponding " - "server implementation must be provided too." - ) + msg = "If an application instance its corresponding server implementation must be provided too." + raise ValueError(msg) self._app = app self.implementation = implementation or default_server @@ -67,7 +68,7 @@ def log_records(self) -> list[logging.LogRecord]: """A list of captured log records""" return self._records - def url(self, path: str = "", query: Optional[Any] = None) -> str: + def url(self, path: str = "", query: Any | None = None) -> str: """Return a URL string pointing to the host and point of the server Args: @@ -88,7 +89,7 @@ def url(self, path: str = "", query: Optional[Any] = None) -> str: def list_logged_exceptions( self, pattern: str = "", - types: Union[Type[Any], Tuple[Type[Any], ...]] = Exception, + types: type[Any] | tuple[type[Any], ...] = Exception, log_level: int = logging.ERROR, del_log_records: bool = True, ) -> list[BaseException]: @@ -132,7 +133,7 @@ async def stop_server() -> None: try: await asyncio.wait_for(started.wait(), timeout=self.timeout) - except Exception: # pragma: no cover + except Exception: # nocov # see if we can await the future for a more helpful error await asyncio.wait_for(server_future, timeout=self.timeout) raise @@ -141,25 +142,24 @@ async def stop_server() -> None: async def __aexit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: await self._exit_stack.aclose() self.mount(None) # reset the view logged_errors = self.list_logged_exceptions(del_log_records=False) - if logged_errors: # pragma: no cover - raise LogAssertionError("Unexpected logged exception") from logged_errors[0] - - return None + if logged_errors: # nocov + msg = "Unexpected logged exception" + raise LogAssertionError(msg) from logged_errors[0] _MountFunc = Callable[["Callable[[], Any] | None"], None] -def _hotswap(update_on_change: bool = False) -> Tuple[_MountFunc, ComponentConstructor]: +def _hotswap(update_on_change: bool = False) -> tuple[_MountFunc, ComponentConstructor]: """Swap out components from a layout on the fly. Since you can't change the component functions used to create a layout @@ -218,8 +218,6 @@ def swap(constructor: Callable[[], Any] | None) -> None: for set_constructor in set_constructor_callbacks: set_constructor(constructor) - return None - else: @component @@ -228,6 +226,5 @@ def HotSwap() -> Any: def swap(constructor: Callable[[], Any] | None) -> None: constructor_ref.current = constructor or (lambda: None) - return None return swap, HotSwap diff --git a/src/reactpy/testing/common.py b/src/py/reactpy/reactpy/testing/common.py similarity index 93% rename from src/reactpy/testing/common.py rename to src/py/reactpy/reactpy/testing/common.py index 61b4a9a0e..945c1c31d 100644 --- a/src/reactpy/testing/common.py +++ b/src/py/reactpy/reactpy/testing/common.py @@ -4,8 +4,9 @@ import inspect import shutil import time +from collections.abc import Awaitable from functools import wraps -from typing import Any, Awaitable, Callable, Generic, Optional, TypeVar, cast +from typing import Any, Callable, Generic, TypeVar, cast from uuid import uuid4 from weakref import ref @@ -65,11 +66,9 @@ async def until( result = await self._func(*self._args, **self._kwargs) if condition(result): break - elif (time.time() - started_at) > timeout: # pragma: no cover - raise TimeoutError( - f"Expected {description} after {timeout} " - f"seconds - last value was {result!r}" - ) + elif (time.time() - started_at) > timeout: # nocov + msg = f"Expected {description} after {timeout} seconds - last value was {result!r}" + raise TimeoutError(msg) async def until_is( self, @@ -126,7 +125,7 @@ def MyComponent(thing): latest: LifeCycleHook - def __init__(self, index_by_kwarg: Optional[str] = None): + def __init__(self, index_by_kwarg: str | None = None): self.index_by_kwarg = index_by_kwarg self.index: dict[Any, LifeCycleHook] = {} @@ -142,7 +141,8 @@ def capture(self, render_function: Callable[..., Any]) -> Callable[..., Any]: @wraps(render_function) def wrapper(*args: Any, **kwargs: Any) -> Any: self = self_ref() - assert self is not None, "Hook catcher has been garbage collected" + if self is None: + raise RuntimeError("Hook catcher has been garbage collected") hook = current_hook() if self.index_by_kwarg is not None: diff --git a/src/reactpy/testing/display.py b/src/py/reactpy/reactpy/testing/display.py similarity index 93% rename from src/reactpy/testing/display.py rename to src/py/reactpy/reactpy/testing/display.py index e97fb5927..bb0d8351d 100644 --- a/src/reactpy/testing/display.py +++ b/src/py/reactpy/reactpy/testing/display.py @@ -13,10 +13,9 @@ ) from reactpy.config import REACTPY_TESTING_DEFAULT_TIMEOUT +from reactpy.testing.backend import BackendFixture from reactpy.types import RootComponentConstructor -from .backend import BackendFixture - class DisplayFixture: """A fixture for running web-based tests using ``playwright``""" @@ -57,8 +56,9 @@ async def goto( async def root_element(self) -> ElementHandle: element = await self.page.wait_for_selector("#app", state="attached") - if element is None: - raise RuntimeError("Root element not attached") # pragma: no cover + if element is None: # nocov + msg = "Root element not attached" + raise RuntimeError(msg) return element async def __aenter__(self) -> DisplayFixture: diff --git a/src/reactpy/testing/logs.py b/src/py/reactpy/reactpy/testing/logs.py similarity index 98% rename from src/reactpy/testing/logs.py rename to src/py/reactpy/reactpy/testing/logs.py index 0a0bebf7a..e9337b19c 100644 --- a/src/reactpy/testing/logs.py +++ b/src/py/reactpy/reactpy/testing/logs.py @@ -2,9 +2,10 @@ import logging import re +from collections.abc import Iterator from contextlib import contextmanager from traceback import format_exception -from typing import Any, Iterator, NoReturn +from typing import Any, NoReturn from reactpy.logging import ROOT_LOGGER @@ -60,7 +61,7 @@ def assert_reactpy_did_log( ) ): break - else: # pragma: no cover + else: # nocov _raise_log_message_error( "Could not find a log record matching the given", match_message, diff --git a/src/reactpy/types.py b/src/py/reactpy/reactpy/types.py similarity index 81% rename from src/reactpy/types.py rename to src/py/reactpy/reactpy/types.py index b8ed1ca2a..715b66fff 100644 --- a/src/reactpy/types.py +++ b/src/py/reactpy/reactpy/types.py @@ -4,10 +4,10 @@ - :mod:`reactpy.backend.types` """ -from .backend.types import BackendImplementation, Connection, Location -from .core.component import Component -from .core.hooks import Context -from .core.types import ( +from reactpy.backend.types import BackendImplementation, Connection, Location +from reactpy.core.component import Component +from reactpy.core.hooks import Context +from reactpy.core.types import ( ComponentConstructor, ComponentType, EventHandlerDict, @@ -26,7 +26,6 @@ VdomJson, ) - __all__ = [ "BackendImplementation", "Component", diff --git a/src/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py similarity index 90% rename from src/reactpy/utils.py rename to src/py/reactpy/reactpy/utils.py index e42097aa0..fead642f2 100644 --- a/src/reactpy/utils.py +++ b/src/py/reactpy/reactpy/utils.py @@ -1,8 +1,9 @@ from __future__ import annotations import re +from collections.abc import Iterable from itertools import chain -from typing import Any, Callable, Generic, Iterable, TypeVar, cast +from typing import Any, Callable, Generic, TypeVar, cast from lxml import etree from lxml.html import fromstring, tostring @@ -10,7 +11,6 @@ from reactpy.core.types import VdomDict from reactpy.core.vdom import vdom - _RefValue = TypeVar("_RefValue") _ModelTransform = Callable[[VdomDict], Any] _UNDEFINED: Any = object() @@ -95,8 +95,9 @@ def html_to_vdom( If ``True``, raise an exception if the HTML does not perfectly follow HTML5 syntax. """ - if not isinstance(html, str): # pragma: no cover - raise TypeError(f"Expected html to be a string, not {type(html).__name__}") + if not isinstance(html, str): # nocov + msg = f"Expected html to be a string, not {type(html).__name__}" + raise TypeError(msg) # If the user provided a string, convert it to a list of lxml.etree nodes try: @@ -111,14 +112,9 @@ def html_to_vdom( ) except etree.XMLSyntaxError as e: if not strict: - raise e # pragma: no cover - raise HTMLParseError( - "An error has occurred while parsing the HTML.\n\n" - "This HTML may be malformatted, or may not perfectly adhere to HTML5.\n" - "If you believe the exception above was due to something intentional, " - "you can disable the strict parameter on html_to_vdom().\n" - "Otherwise, repair your broken HTML and try again." - ) from e + raise e # nocov + msg = "An error has occurred while parsing the HTML.\n\nThis HTML may be malformatted, or may not perfectly adhere to HTML5.\nIf you believe the exception above was due to something intentional, you can disable the strict parameter on html_to_vdom().\nOtherwise, repair your broken HTML and try again." + raise HTMLParseError(msg) from e return _etree_to_vdom(root_node, transforms) @@ -140,10 +136,9 @@ def _etree_to_vdom( dictionary which will be replaced by ``new``. For example, you could use a transform function to add highlighting to a ```` block. """ - if not isinstance(node, etree._Element): # pragma: no cover - raise TypeError( - f"Expected node to be a etree._Element, not {type(node).__name__}" - ) + if not isinstance(node, etree._Element): # nocov + msg = f"Expected node to be a etree._Element, not {type(node).__name__}" + raise TypeError(msg) # Recursively call _etree_to_vdom() on all children children = _generate_vdom_children(node, transforms) @@ -165,7 +160,8 @@ def _add_vdom_to_etree(parent: etree._Element, vdom: VdomDict | dict[str, Any]) try: tag = vdom["tagName"] except KeyError as e: - raise TypeError(f"Expected a VDOM dict, not {vdom}") from e + msg = f"Expected a VDOM dict, not {vdom}" + raise TypeError(msg) from e else: vdom = cast(VdomDict, vdom) @@ -218,7 +214,7 @@ def _mutate_vdom(vdom: VdomDict) -> None: and isinstance(vdom["attributes"]["style"], str) ): # Convince type checker that it's safe to mutate attributes - assert isinstance(vdom["attributes"], dict) + assert isinstance(vdom["attributes"], dict) # noqa: S101 # Convert style attribute from str -> dict with camelCase keys vdom["attributes"]["style"] = { @@ -295,9 +291,8 @@ def _vdom_attr_to_html_str(key: str, value: Any) -> tuple[str, str]: ): key = _CAMEL_CASE_SUB_PATTERN.sub("-", key) - assert not callable( - value - ), f"Could not convert callable attribute {key}={value} to HTML" + if callable(value): # nocov + raise TypeError(f"Cannot convert callable attribute {key}={value} to HTML") # Again, we lower the attribute name only to normalize - HTML is case-insensitive: # http://w3c.github.io/html-reference/documents.html#case-insensitivity diff --git a/src/reactpy/web/__init__.py b/src/py/reactpy/reactpy/web/__init__.py similarity index 87% rename from src/reactpy/web/__init__.py rename to src/py/reactpy/reactpy/web/__init__.py index 6fe239ed9..308429dbb 100644 --- a/src/reactpy/web/__init__.py +++ b/src/py/reactpy/reactpy/web/__init__.py @@ -1,4 +1,4 @@ -from .module import ( +from reactpy.web.module import ( export, module_from_file, module_from_string, @@ -6,7 +6,6 @@ module_from_url, ) - __all__ = [ "module_from_file", "module_from_string", diff --git a/src/reactpy/web/module.py b/src/py/reactpy/reactpy/web/module.py similarity index 95% rename from src/reactpy/web/module.py rename to src/py/reactpy/reactpy/web/module.py index efdd2395f..5e615a981 100644 --- a/src/reactpy/web/module.py +++ b/src/py/reactpy/reactpy/web/module.py @@ -13,14 +13,12 @@ from reactpy.config import REACTPY_DEBUG_MODE, REACTPY_WEB_MODULES_DIR from reactpy.core.types import ImportSourceDict, VdomDictConstructor from reactpy.core.vdom import make_vdom_constructor - -from .utils import ( +from reactpy.web.utils import ( module_name_suffix, resolve_module_exports_from_file, resolve_module_exports_from_url, ) - logger = logging.getLogger(__name__) SourceType = NewType("SourceType", str) @@ -143,7 +141,8 @@ def module_from_template( template_file = Path(__file__).parent / "templates" / template_file_name if not template_file.exists(): - raise ValueError(f"No template for {template_file_name!r} exists") + msg = f"No template for {template_file_name!r} exists" + raise ValueError(msg) variables = {"PACKAGE": package, "CDN": cdn, "VERSION": template_version} content = Template(template_file.read_text()).substitute(variables) @@ -193,7 +192,8 @@ def module_from_file( source_file = Path(file).resolve() target_file = _web_module_path(name) if not source_file.exists(): - raise FileNotFoundError(f"Source file does not exist: {source_file}") + msg = f"Source file does not exist: {source_file}" + raise FileNotFoundError(msg) if not target_file.exists(): _copy_file(target_file, source_file, symlink) @@ -351,15 +351,15 @@ def export( web_module.export_names is not None and export_names not in web_module.export_names ): - raise ValueError(f"{web_module.source!r} does not export {export_names!r}") + msg = f"{web_module.source!r} does not export {export_names!r}" + raise ValueError(msg) return _make_export(web_module, export_names, fallback, allow_children) else: if web_module.export_names is not None: - missing = list( - sorted(set(export_names).difference(web_module.export_names)) - ) + missing = sorted(set(export_names).difference(web_module.export_names)) if missing: - raise ValueError(f"{web_module.source!r} does not export {missing!r}") + msg = f"{web_module.source!r} does not export {missing!r}" + raise ValueError(msg) return [ _make_export(web_module, name, fallback, allow_children) for name in export_names diff --git a/src/reactpy/web/templates/react.js b/src/py/reactpy/reactpy/web/templates/react.js similarity index 99% rename from src/reactpy/web/templates/react.js rename to src/py/reactpy/reactpy/web/templates/react.js index 00924f85a..5c6a45743 100644 --- a/src/reactpy/web/templates/react.js +++ b/src/py/reactpy/reactpy/web/templates/react.js @@ -54,7 +54,7 @@ function makeJsonSafeEventHandler(oldHandler) { } return true; } - }) + }), ); }; } diff --git a/src/reactpy/web/utils.py b/src/py/reactpy/reactpy/web/utils.py similarity index 96% rename from src/reactpy/web/utils.py rename to src/py/reactpy/reactpy/web/utils.py index a1f812187..cf8b8638b 100644 --- a/src/reactpy/web/utils.py +++ b/src/py/reactpy/reactpy/web/utils.py @@ -1,12 +1,10 @@ import logging import re from pathlib import Path, PurePosixPath -from typing import Set, Tuple from urllib.parse import urlparse import requests - logger = logging.getLogger(__name__) @@ -22,7 +20,7 @@ def resolve_module_exports_from_file( file: Path, max_depth: int, is_re_export: bool = False, -) -> Set[str]: +) -> set[str]: if max_depth == 0: logger.warning(f"Did not resolve all exports for {file} - max depth reached") return set() @@ -52,13 +50,13 @@ def resolve_module_exports_from_url( url: str, max_depth: int, is_re_export: bool = False, -) -> Set[str]: +) -> set[str]: if max_depth == 0: logger.warning(f"Did not resolve all exports for {url} - max depth reached") return set() try: - text = requests.get(url).text + text = requests.get(url, timeout=5).text except requests.exceptions.ConnectionError as error: reason = "" if error is None else " - {error.errno}" logger.warning("Did not resolve exports for url " + url + reason) @@ -79,9 +77,9 @@ def resolve_module_exports_from_url( def resolve_module_exports_from_source( content: str, exclude_default: bool -) -> Tuple[Set[str], Set[str]]: - names: Set[str] = set() - references: Set[str] = set() +) -> tuple[set[str], set[str]]: + names: set[str] = set() + references: set[str] = set() if _JS_DEFAULT_EXPORT_PATTERN.search(content): names.add("default") diff --git a/src/reactpy/widgets.py b/src/py/reactpy/reactpy/widgets.py similarity index 85% rename from src/reactpy/widgets.py rename to src/py/reactpy/reactpy/widgets.py index 584c3b562..cc19be04d 100644 --- a/src/reactpy/widgets.py +++ b/src/py/reactpy/reactpy/widgets.py @@ -1,20 +1,18 @@ from __future__ import annotations from base64 import b64encode -from typing import TYPE_CHECKING, Any, Callable, Sequence, Tuple, TypeVar, Union - -from typing_extensions import Protocol +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar import reactpy - -from . import html -from ._warnings import warn -from .core.types import ComponentConstructor, VdomDict +from reactpy import html +from reactpy._warnings import warn +from reactpy.core.types import ComponentConstructor, VdomDict def image( format: str, - value: Union[str, bytes] = "", + value: str | bytes = "", attributes: dict[str, Any] | None = None, ) -> VdomDict: """Utility for constructing an image from a string or bytes @@ -22,7 +20,7 @@ def image( The source value will automatically be encoded to base64 """ if format == "svg": - format = "svg+xml" + format = "svg+xml" # noqa: A001 if isinstance(value, str): bytes_value = value.encode() @@ -89,17 +87,17 @@ def __call__(self, value: str) -> _CastTo: if TYPE_CHECKING: - from .testing.backend import _MountFunc + from reactpy.testing.backend import _MountFunc def hotswap( update_on_change: bool = False, -) -> Tuple[_MountFunc, ComponentConstructor]: # pragma: no cover +) -> tuple[_MountFunc, ComponentConstructor]: # nocov warn( "The 'hotswap' function is deprecated and will be removed in a future release", DeprecationWarning, stacklevel=2, ) - from .testing.backend import _hotswap + from reactpy.testing.backend import _hotswap return _hotswap(update_on_change) diff --git a/tests/__init__.py b/src/py/reactpy/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to src/py/reactpy/tests/__init__.py diff --git a/tests/conftest.py b/src/py/reactpy/tests/conftest.py similarity index 97% rename from tests/conftest.py rename to src/py/reactpy/tests/conftest.py index ceadbbb78..1a1f02098 100644 --- a/tests/conftest.py +++ b/src/py/reactpy/tests/conftest.py @@ -57,7 +57,7 @@ async def browser(pytestconfig: Config): @pytest.fixture(scope="session") def event_loop(): - if os.name == "nt": # pragma: no cover + if os.name == "nt": # nocov asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) with open_event_loop() as loop: yield loop diff --git a/tests/test__console/__init__.py b/src/py/reactpy/tests/test__console/__init__.py similarity index 100% rename from tests/test__console/__init__.py rename to src/py/reactpy/tests/test__console/__init__.py diff --git a/tests/test__console/test_rewrite_camel_case_props.py b/src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py similarity index 99% rename from tests/test__console/test_rewrite_camel_case_props.py rename to src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py index d8746f8c7..47b8baabc 100644 --- a/tests/test__console/test_rewrite_camel_case_props.py +++ b/src/py/reactpy/tests/test__console/test_rewrite_camel_case_props.py @@ -10,7 +10,6 @@ rewrite_camel_case_props, ) - if sys.version_info < (3, 9): pytestmark = pytest.mark.skip(reason="ast.unparse is Python>=3.9") diff --git a/tests/test__console/test_rewrite_keys.py b/src/py/reactpy/tests/test__console/test_rewrite_keys.py similarity index 99% rename from tests/test__console/test_rewrite_keys.py rename to src/py/reactpy/tests/test__console/test_rewrite_keys.py index 6fad036e7..da0b26c4f 100644 --- a/tests/test__console/test_rewrite_keys.py +++ b/src/py/reactpy/tests/test__console/test_rewrite_keys.py @@ -7,7 +7,6 @@ from reactpy._console.rewrite_keys import generate_rewrite, rewrite_keys - if sys.version_info < (3, 9): pytestmark = pytest.mark.skip(reason="ast.unparse is Python>=3.9") diff --git a/tests/test__option.py b/src/py/reactpy/tests/test__option.py similarity index 98% rename from tests/test__option.py rename to src/py/reactpy/tests/test__option.py index 1fbd6459e..63f2fada8 100644 --- a/tests/test__option.py +++ b/src/py/reactpy/tests/test__option.py @@ -105,7 +105,7 @@ def test_deprecated_option(): opt = DeprecatedOption("is deprecated!", "A_FAKE_OPTION", None) with pytest.warns(DeprecationWarning, match="is deprecated!"): - opt.current + assert opt.current is None with pytest.warns(DeprecationWarning, match="is deprecated!"): opt.current = "something" diff --git a/tests/test_backend/__init__.py b/src/py/reactpy/tests/test_backend/__init__.py similarity index 100% rename from tests/test_backend/__init__.py rename to src/py/reactpy/tests/test_backend/__init__.py diff --git a/tests/test_backend/test__common.py b/src/py/reactpy/tests/test_backend/test__common.py similarity index 100% rename from tests/test_backend/test__common.py rename to src/py/reactpy/tests/test_backend/test__common.py diff --git a/tests/test_backend/test_all.py b/src/py/reactpy/tests/test_backend/test_all.py similarity index 97% rename from tests/test_backend/test_all.py rename to src/py/reactpy/tests/test_backend/test_all.py index fcf6b7286..796f096db 100644 --- a/tests/test_backend/test_all.py +++ b/src/py/reactpy/tests/test_backend/test_all.py @@ -1,4 +1,4 @@ -from typing import MutableMapping +from collections.abc import MutableMapping import pytest @@ -12,7 +12,7 @@ @pytest.fixture( - params=list(all_implementations()) + [default_implementation], + params=[*list(all_implementations()), default_implementation], ids=lambda imp: imp.__name__, scope="module", ) diff --git a/tests/test_backend/test_utils.py b/src/py/reactpy/tests/test_backend/test_utils.py similarity index 95% rename from tests/test_backend/test_utils.py rename to src/py/reactpy/tests/test_backend/test_utils.py index 2b36966f5..2a58dc62a 100644 --- a/tests/test_backend/test_utils.py +++ b/src/py/reactpy/tests/test_backend/test_utils.py @@ -8,7 +8,7 @@ from reactpy.backend import flask as flask_implementation from reactpy.backend.utils import find_available_port from reactpy.backend.utils import run as sync_run -from reactpy.sample import SampleApp as SampleApp +from reactpy.sample import SampleApp @pytest.fixture diff --git a/tests/test_client.py b/src/py/reactpy/tests/test_client.py similarity index 98% rename from tests/test_client.py rename to src/py/reactpy/tests/test_client.py index ea6a3a672..5a51f8b16 100644 --- a/tests/test_client.py +++ b/src/py/reactpy/tests/test_client.py @@ -10,7 +10,6 @@ from tests.tooling.common import DEFAULT_TYPE_DELAY from tests.tooling.hooks import use_counter - JS_DIR = Path(__file__).parent / "js" @@ -27,7 +26,7 @@ def SomeComponent(): return reactpy.html._( reactpy.html.p({"data_count": count, "id": "count"}, "count", count), reactpy.html.button( - dict(on_click=lambda e: incr_count(), id="incr"), "incr" + {"on_click": lambda e: incr_count(), "id": "incr"}, "incr" ), ) diff --git a/tests/test_config.py b/src/py/reactpy/tests/test_config.py similarity index 100% rename from tests/test_config.py rename to src/py/reactpy/tests/test_config.py diff --git a/tests/test_core/__init__.py b/src/py/reactpy/tests/test_core/__init__.py similarity index 100% rename from tests/test_core/__init__.py rename to src/py/reactpy/tests/test_core/__init__.py diff --git a/tests/test_core/test_component.py b/src/py/reactpy/tests/test_core/test_component.py similarity index 100% rename from tests/test_core/test_component.py rename to src/py/reactpy/tests/test_core/test_component.py diff --git a/tests/test_core/test_events.py b/src/py/reactpy/tests/test_core/test_events.py similarity index 99% rename from tests/test_core/test_events.py rename to src/py/reactpy/tests/test_core/test_events.py index f6ab2bdc1..67c7c2ba9 100644 --- a/tests/test_core/test_events.py +++ b/src/py/reactpy/tests/test_core/test_events.py @@ -193,7 +193,7 @@ def inner_click_no_op(event): clicked.current = True def outer_click_is_not_triggered(event): - assert False + raise AssertionError() outer = reactpy.html.div( { diff --git a/tests/test_core/test_hooks.py b/src/py/reactpy/tests/test_core/test_hooks.py similarity index 97% rename from tests/test_core/test_hooks.py rename to src/py/reactpy/tests/test_core/test_hooks.py index 570319677..ce9afdc12 100644 --- a/tests/test_core/test_hooks.py +++ b/src/py/reactpy/tests/test_core/test_hooks.py @@ -557,7 +557,8 @@ async def test_error_in_effect_is_gracefully_handled(caplog): def ComponentWithEffect(): @reactpy.hooks.use_effect def bad_effect(): - raise ValueError("Something went wong :(") + msg = "Something went wong :(" + raise ValueError(msg) return reactpy.html.div() @@ -566,29 +567,6 @@ def bad_effect(): await layout.render() # no error -async def test_error_in_effect_cleanup_is_gracefully_handled(caplog): - caplog.clear() - component_hook = HookCatcher() - - @reactpy.component - @component_hook.capture - def ComponentWithEffect(): - @reactpy.hooks.use_effect(dependencies=None) # force this to run every time - def ok_effect(): - def bad_cleanup(): - raise ValueError("Something went wong :(") - - return bad_cleanup - - return reactpy.html.div() - - with assert_reactpy_did_log(match_error=r"Layout post-render effect .* failed"): - async with reactpy.Layout(ComponentWithEffect()) as layout: - await layout.render() - component_hook.latest.schedule_render() - await layout.render() # no error - - async def test_error_in_effect_pre_unmount_cleanup_is_gracefully_handled(): set_key = reactpy.Ref() @@ -602,7 +580,8 @@ def ComponentWithEffect(): @reactpy.hooks.use_effect def ok_effect(): def bad_cleanup(): - raise ValueError("Something went wong :(") + msg = "Something went wong :(" + raise ValueError(msg) return bad_cleanup @@ -628,7 +607,8 @@ def reducer(count, action): elif action == "decrement": return count - 1 else: - raise ValueError(f"Unknown action '{action}'") + msg = f"Unknown action '{action}'" + raise ValueError(msg) @reactpy.component def Counter(initial_count): @@ -660,7 +640,8 @@ def reducer(count, action): if action == "increment": return count + 1 else: - raise ValueError(f"Unknown action '{action}'") + msg = f"Unknown action '{action}'" + raise ValueError(msg) @reactpy.component def ComponentWithUseReduce(): @@ -792,7 +773,7 @@ async def test_use_memo_with_stored_deps_is_empty_tuple_after_deps_are_none(): def ComponentWithMemo(): value = reactpy.hooks.use_memo( lambda: next(iter_values), - deps_used_in_memo.current, # noqa: ROH202 + deps_used_in_memo.current, ) used_values.append(value) return reactpy.html.div() @@ -853,7 +834,8 @@ def ComponentWithRef(): def test_bad_schedule_render_callback(): def bad_callback(): - raise ValueError("something went wrong") + msg = "something went wrong" + raise ValueError(msg) with assert_reactpy_did_log( match_message=f"Failed to schedule render via {bad_callback}" @@ -872,7 +854,7 @@ def CounterWithEffect(): @reactpy.hooks.use_effect def some_effect_that_uses_count(): """should automatically trigger on count change""" - count # use count in this closure + _ = count # use count in this closure did_effect.set() return reactpy.html.div() @@ -900,7 +882,7 @@ def CounterWithEffect(): @reactpy.hooks.use_memo def some_memo_func_that_uses_count(): """should automatically trigger on count change""" - count # use count in this closure + _ = count # use count in this closure did_memo.set() return reactpy.html.div() @@ -935,11 +917,11 @@ def ComponentUsesContext(): assert value.current == "something" @reactpy.component - def ComponentUsesContext(): + def ComponentUsesContext2(): value.current = reactpy.use_context(Context) return html.div() - async with reactpy.Layout(ComponentUsesContext()) as layout: + async with reactpy.Layout(ComponentUsesContext2()) as layout: await layout.render() assert value.current == "something" @@ -1011,22 +993,21 @@ def Child2(): await layout.render() -async def test_error_in_effect_cleanup_is_gracefully_handled(): +async def test_error_in_layout_effect_cleanup_is_gracefully_handled(): component_hook = HookCatcher() @reactpy.component @component_hook.capture def ComponentWithEffect(): - hook = current_hook() - + @reactpy.hooks.use_effect(dependencies=None) # always run def bad_effect(): - raise ValueError("The error message") + msg = "The error message" + raise ValueError(msg) - hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect) return reactpy.html.div() with assert_reactpy_did_log( - match_message="Component post-render effect .*? failed", + match_message=r"post-render effect .*? failed", error_type=ValueError, match_error="The error message", ): @@ -1244,7 +1225,6 @@ async def test_use_state_named_tuple(): @reactpy.component def some_component(): state.current = reactpy.use_state(1) - return None async with reactpy.Layout(some_component()) as layout: await layout.render() @@ -1252,3 +1232,28 @@ def some_component(): state.current.set_value(2) await layout.render() assert state.current.value == 2 + + +async def test_error_in_component_effect_cleanup_is_gracefully_handled(): + component_hook = HookCatcher() + + @reactpy.component + @component_hook.capture + def ComponentWithEffect(): + hook = current_hook() + + def bad_effect(): + raise ValueError("The error message") + + hook.add_effect(COMPONENT_DID_RENDER_EFFECT, bad_effect) + return reactpy.html.div() + + with assert_reactpy_did_log( + match_message="Component post-render effect .*? failed", + error_type=ValueError, + match_error="The error message", + ): + async with reactpy.Layout(ComponentWithEffect()) as layout: + await layout.render() + component_hook.latest.schedule_render() + await layout.render() # no error diff --git a/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py similarity index 98% rename from tests/test_core/test_layout.py rename to src/py/reactpy/tests/test_core/test_layout.py index d46913314..4aa31fe73 100644 --- a/tests/test_core/test_layout.py +++ b/src/py/reactpy/tests/test_core/test_layout.py @@ -58,13 +58,10 @@ def Component(): component = Component() layout = reactpy.Layout(component) - with pytest.raises(Exception): + with pytest.raises(AttributeError): await layout.deliver(event_message("something")) - with pytest.raises(Exception): - layout.update(component) - - with pytest.raises(Exception): + with pytest.raises(AttributeError): await layout.render() @@ -171,7 +168,8 @@ def OkChild(): @reactpy.component def BadChild(): - raise ValueError("error from bad child") + msg = "error from bad child" + raise ValueError(msg) with assert_reactpy_did_log(match_error="error from bad child"): async with reactpy.Layout(Main()) as layout: @@ -221,7 +219,8 @@ def OkChild(): @reactpy.component def BadChild(): - raise ValueError("error from bad child") + msg = "error from bad child" + raise ValueError(msg) with assert_reactpy_did_log(match_error="error from bad child"): async with reactpy.Layout(Main()) as layout: @@ -385,7 +384,7 @@ def wrapper(*args, **kwargs): @reactpy.component @add_to_live_hooks def Outer(): - nonlocal set_inner_component + nonlocal set_inner_component # noqa PLE0117 inner_component, set_inner_component = reactpy.hooks.use_state( Inner(key="first") ) @@ -504,7 +503,8 @@ def good_trigger(): @bad_handler.use def bad_trigger(): - raise ValueError("Called bad trigger") + msg = "Called bad trigger" + raise ValueError(msg) children = [ reactpy.html.button( @@ -522,7 +522,7 @@ def bad_trigger(): async with reactpy.Layout(MyComponent()) as layout: await layout.render() - for i in range(3): + for _i in range(3): event = event_message(good_handler.target) await layout.deliver(event) @@ -567,7 +567,8 @@ def callback(): @bad_handler.use def callback(): - raise ValueError("Called bad trigger") + msg = "Called bad trigger" + raise ValueError(msg) return reactpy.html.button({"on_click": callback, "id": "good"}, "good") @@ -658,7 +659,7 @@ def HasEventHandlerAtRoot(): async with reactpy.Layout(HasEventHandlerAtRoot()) as layout: await layout.render() - for i in range(3): + for _i in range(3): last_event_handler = event_handler.current # after this render we should have release the reference to the last handler await layout.render() @@ -680,7 +681,7 @@ def HasNestedEventHandler(): async with reactpy.Layout(HasNestedEventHandler()) as layout: await layout.render() - for i in range(3): + for _i in range(3): last_event_handler = event_handler.current # after this render we should have release the reference to the last handler await layout.render() @@ -753,7 +754,8 @@ async def test_log_error_on_bad_event_handler(): def ComponentWithBadEventHandler(): @bad_handler.use def raise_error(): - raise Exception("bad event handler") + msg = "bad event handler" + raise Exception(msg) return reactpy.html.button({"on_click": raise_error}) @@ -777,7 +779,6 @@ def Parent(): @reactpy.component @child_hook.capture def Child(state): - reactpy.hooks.use_effect(lambda: lambda: print("unmount", state)) return reactpy.html.div(state) with assert_reactpy_did_log( @@ -893,7 +894,7 @@ def HasState(): async with reactpy.Layout(Root()) as layout: await layout.render() - for i in range(5): + for _i in range(5): last_state = state.current root_hook.latest.schedule_render() await layout.render() @@ -1027,7 +1028,7 @@ async def record_if_state_is_reset(): assert effect_calls_without_state == {"some-key", "key-0"} did_call_effect.clear() - for i in range(1, 5): + for _i in range(1, 5): await layout.deliver(event_message(set_child_key_num.target)) await layout.render() assert effect_calls_without_state == {"some-key", "key-0"} @@ -1167,7 +1168,6 @@ def Parent(): def Child(): nonlocal schedule_removed_child_render schedule_removed_child_render = use_force_render() - return None async with reactpy.Layout(Parent()) as layout: await layout.render() diff --git a/tests/test_core/test_serve.py b/src/py/reactpy/tests/test_core/test_serve.py similarity index 97% rename from tests/test_core/test_serve.py rename to src/py/reactpy/tests/test_core/test_serve.py index b875b3649..4d39f3e3d 100644 --- a/tests/test_core/test_serve.py +++ b/src/py/reactpy/tests/test_core/test_serve.py @@ -1,5 +1,6 @@ import asyncio -from typing import Any, Sequence +from collections.abc import Sequence +from typing import Any from jsonpointer import set_pointer @@ -10,7 +11,6 @@ from reactpy.testing import StaticEventHandler from tests.tooling.common import event_message - EVENT_NAME = "on_event" STATIC_EVENT_HANDLER = StaticEventHandler() @@ -122,7 +122,7 @@ async def handle_event(): send_queue = asyncio.Queue() recv_queue = asyncio.Queue() - asyncio.ensure_future( + task = asyncio.create_task( serve_layout( reactpy.Layout(ComponentWithTwoEventHandlers()), send_queue.put, @@ -135,3 +135,5 @@ async def handle_event(): await recv_queue.put(event_message(non_blocked_handler.target)) await second_event_did_execute.wait() + + task.cancel() diff --git a/tests/test_core/test_vdom.py b/src/py/reactpy/tests/test_core/test_vdom.py similarity index 99% rename from tests/test_core/test_vdom.py rename to src/py/reactpy/tests/test_core/test_vdom.py index 1b86207de..2aac75bb4 100644 --- a/tests/test_core/test_vdom.py +++ b/src/py/reactpy/tests/test_core/test_vdom.py @@ -9,7 +9,6 @@ from reactpy.core.types import VdomDict from reactpy.core.vdom import is_vdom, make_vdom_constructor, validate_vdom_json - FAKE_EVENT_HANDLER = EventHandler(lambda data: None) FAKE_EVENT_HANDLER_DICT = {"on_event": FAKE_EVENT_HANDLER} @@ -70,7 +69,7 @@ def test_is_vdom(result, value): {"tagName": "div", "children": [0, 1, 2]}, ), ( - reactpy.vdom("div", map(lambda x: x**2, [1, 2, 3])), + reactpy.vdom("div", (x**2 for x in [1, 2, 3])), {"tagName": "div", "children": [1, 4, 9]}, ), ], diff --git a/tests/test_html.py b/src/py/reactpy/tests/test_html.py similarity index 100% rename from tests/test_html.py rename to src/py/reactpy/tests/test_html.py diff --git a/tests/test_sample.py b/src/py/reactpy/tests/test_sample.py similarity index 100% rename from tests/test_sample.py rename to src/py/reactpy/tests/test_sample.py diff --git a/tests/test_testing.py b/src/py/reactpy/tests/test_testing.py similarity index 91% rename from tests/test_testing.py rename to src/py/reactpy/tests/test_testing.py index 5651f93bc..760e68af3 100644 --- a/tests/test_testing.py +++ b/src/py/reactpy/tests/test_testing.py @@ -6,7 +6,7 @@ from reactpy import Ref, component, html, testing from reactpy.backend import starlette as starlette_implementation from reactpy.logging import ROOT_LOGGER -from reactpy.sample import SampleApp as SampleApp +from reactpy.sample import SampleApp from reactpy.testing.backend import _hotswap from reactpy.testing.display import DisplayFixture @@ -14,7 +14,8 @@ def test_assert_reactpy_logged_does_not_supress_errors(): with pytest.raises(RuntimeError, match="expected error"): with testing.assert_reactpy_did_log(): - raise RuntimeError("expected error") + msg = "expected error" + raise RuntimeError(msg) def test_assert_reactpy_logged_message(): @@ -32,7 +33,8 @@ def test_assert_reactpy_logged_error(): match_error="my value error", ): try: - raise ValueError("my value error") + msg = "my value error" + raise ValueError(msg) except ValueError: ROOT_LOGGER.exception("log message") @@ -47,7 +49,8 @@ def test_assert_reactpy_logged_error(): ): try: # change error type - raise RuntimeError("my value error") + msg = "my value error" + raise RuntimeError(msg) except RuntimeError: ROOT_LOGGER.exception("log message") @@ -62,7 +65,8 @@ def test_assert_reactpy_logged_error(): ): try: # change error message - raise ValueError("something else") + msg = "something else" + raise ValueError(msg) except ValueError: ROOT_LOGGER.exception("log message") @@ -77,7 +81,8 @@ def test_assert_reactpy_logged_error(): ): try: # change error message - raise ValueError("my error message") + msg = "my error message" + raise ValueError(msg) except ValueError: ROOT_LOGGER.exception("something else") @@ -125,7 +130,8 @@ def test_assert_reactpy_did_not_log(): match_error=r".*", ): try: - raise Exception("something") + msg = "something" + raise Exception(msg) except Exception: ROOT_LOGGER.exception("something") @@ -157,7 +163,8 @@ def test_list_logged_excptions(): ROOT_LOGGER.info("A non-error log message") try: - raise ValueError("An error for testing") + msg = "An error for testing" + raise ValueError(msg) except Exception as error: ROOT_LOGGER.exception("Log the error") the_error = error diff --git a/tests/test_utils.py b/src/py/reactpy/tests/test_utils.py similarity index 99% rename from tests/test_utils.py rename to src/py/reactpy/tests/test_utils.py index d3bc64e04..c764b04af 100644 --- a/tests/test_utils.py +++ b/src/py/reactpy/tests/test_utils.py @@ -24,7 +24,7 @@ def test_basic_ref_behavior(): r = reactpy.Ref() with pytest.raises(AttributeError): - r.current + r.current # noqa B018 r.current = 4 assert r.current == 4 diff --git a/tests/test_web/__init__.py b/src/py/reactpy/tests/test_web/__init__.py similarity index 100% rename from tests/test_web/__init__.py rename to src/py/reactpy/tests/test_web/__init__.py diff --git a/tests/test_web/js_fixtures/component-can-have-child.js b/src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js similarity index 90% rename from tests/test_web/js_fixtures/component-can-have-child.js rename to src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js index e1e892ca0..fd443b164 100644 --- a/tests/test_web/js_fixtures/component-can-have-child.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/component-can-have-child.js @@ -8,7 +8,7 @@ export function bind(node, config) { create: (type, props, children) => h(type, props, ...children), render: (element) => render(element, node), unmount: () => render(null, node), - } + }; } // The intention here is that Child components are passed in here so we check that the @@ -19,9 +19,9 @@ export function Parent(props) {

the parent

    ${props.children} - ` + `; } export function Child({ index }) { - return html`
  • child ${index}
  • ` + return html`
  • child ${index}
  • `; } diff --git a/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js new file mode 100644 index 000000000..372f108bd --- /dev/null +++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/index.js @@ -0,0 +1,2 @@ +export { index as Index }; +export * from "./one.js"; diff --git a/tests/test_web/js_fixtures/export-resolution/one.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js similarity index 88% rename from tests/test_web/js_fixtures/export-resolution/one.js rename to src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js index 1d404b1cc..ddea9d2cb 100644 --- a/tests/test_web/js_fixtures/export-resolution/one.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/one.js @@ -1,4 +1,4 @@ -export {one as One}; +export { one as One }; // use ../ just to check that it works export * from "../export-resolution/two.js"; // this default should not be exported by the * re-export in index.js diff --git a/tests/test_web/js_fixtures/export-resolution/two.js b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js similarity index 65% rename from tests/test_web/js_fixtures/export-resolution/two.js rename to src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js index 4e1d807c2..f01389d2e 100644 --- a/tests/test_web/js_fixtures/export-resolution/two.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/export-resolution/two.js @@ -1,2 +1,2 @@ -export {two as Two}; +export { two as Two }; export * from "https://some.external.url"; diff --git a/tests/test_web/js_fixtures/exports-syntax.js b/src/py/reactpy/tests/test_web/js_fixtures/exports-syntax.js similarity index 100% rename from tests/test_web/js_fixtures/exports-syntax.js rename to src/py/reactpy/tests/test_web/js_fixtures/exports-syntax.js diff --git a/tests/test_web/js_fixtures/exports-two-components.js b/src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js similarity index 80% rename from tests/test_web/js_fixtures/exports-two-components.js rename to src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js index e647ac928..10aa7fdbe 100644 --- a/tests/test_web/js_fixtures/exports-two-components.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/exports-two-components.js @@ -8,13 +8,13 @@ export function bind(node, config) { create: (type, props, children) => h(type, props, ...children), render: (element) => render(element, node), unmount: () => render(null, node), - } + }; } export function Header1(props) { - return h("h1", {id: props.id}, props.text); + return h("h1", { id: props.id }, props.text); } export function Header2(props) { - return h("h2", {id: props.id}, props.text); + return h("h2", { id: props.id }, props.text); } diff --git a/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js b/src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js similarity index 99% rename from tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js rename to src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js index a58d89e9f..dbb1a1c99 100644 --- a/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/set-flag-when-unmount-is-called.js @@ -3,7 +3,7 @@ export function bind(node, config) { create: (type, props, children) => type(props), render: (element) => renderElement(element, node), unmount: () => unmountElement(node), - } + }; } export function renderElement(element, container) { diff --git a/tests/test_web/js_fixtures/simple-button.js b/src/py/reactpy/tests/test_web/js_fixtures/simple-button.js similarity index 95% rename from tests/test_web/js_fixtures/simple-button.js rename to src/py/reactpy/tests/test_web/js_fixtures/simple-button.js index 00b094138..2b49f505b 100644 --- a/tests/test_web/js_fixtures/simple-button.js +++ b/src/py/reactpy/tests/test_web/js_fixtures/simple-button.js @@ -8,7 +8,7 @@ export function bind(node, config) { create: (type, props, children) => h(type, props, ...children), render: (element) => render(element, node), unmount: () => render(null, node), - } + }; } export function SimpleButton(props) { @@ -20,6 +20,6 @@ export function SimpleButton(props) { props.onClick({ data: props.eventResponseData }); }, }, - "simple button" + "simple button", ); } diff --git a/tests/test_web/test_module.py b/src/py/reactpy/tests/test_web/test_module.py similarity index 99% rename from tests/test_web/test_module.py rename to src/py/reactpy/tests/test_web/test_module.py index 4b0de2af1..f8783337d 100644 --- a/tests/test_web/test_module.py +++ b/src/py/reactpy/tests/test_web/test_module.py @@ -14,7 +14,6 @@ ) from reactpy.web.module import NAME_SOURCE, WebModule - JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures" diff --git a/tests/test_web/test_utils.py b/src/py/reactpy/tests/test_web/test_utils.py similarity index 99% rename from tests/test_web/test_utils.py rename to src/py/reactpy/tests/test_web/test_utils.py index 4ed01dd83..14c3e2e13 100644 --- a/tests/test_web/test_utils.py +++ b/src/py/reactpy/tests/test_web/test_utils.py @@ -11,7 +11,6 @@ resolve_module_exports_from_url, ) - JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures" diff --git a/tests/test_widgets.py b/src/py/reactpy/tests/test_widgets.py similarity index 99% rename from tests/test_widgets.py rename to src/py/reactpy/tests/test_widgets.py index e4ac1a341..d786fded0 100644 --- a/tests/test_widgets.py +++ b/src/py/reactpy/tests/test_widgets.py @@ -5,7 +5,6 @@ from reactpy.testing import DisplayFixture, poll from tests.tooling.common import DEFAULT_TYPE_DELAY - HERE = Path(__file__).parent diff --git a/tests/tooling/__init__.py b/src/py/reactpy/tests/tooling/__init__.py similarity index 100% rename from tests/tooling/__init__.py rename to src/py/reactpy/tests/tooling/__init__.py diff --git a/tests/tooling/asserts.py b/src/py/reactpy/tests/tooling/asserts.py similarity index 62% rename from tests/tooling/asserts.py rename to src/py/reactpy/tests/tooling/asserts.py index 49105fb80..ac84aa0ba 100644 --- a/tests/tooling/asserts.py +++ b/src/py/reactpy/tests/tooling/asserts.py @@ -1,5 +1,5 @@ def assert_same_items(left, right): """Check that two unordered sequences are equal (only works if reprs are equal)""" - sorted_left = list(sorted(left, key=repr)) - sorted_right = list(sorted(right, key=repr)) + sorted_left = sorted(left, key=repr) + sorted_right = sorted(right, key=repr) assert sorted_left == sorted_right diff --git a/tests/tooling/common.py b/src/py/reactpy/tests/tooling/common.py similarity index 99% rename from tests/tooling/common.py rename to src/py/reactpy/tests/tooling/common.py index 8a929edce..b84a3ac96 100644 --- a/tests/tooling/common.py +++ b/src/py/reactpy/tests/tooling/common.py @@ -2,7 +2,6 @@ from reactpy.core.types import LayoutEventMessage, LayoutUpdateMessage - # see: https://github.com/microsoft/playwright-python/issues/1614 DEFAULT_TYPE_DELAY = 100 # miliseconds diff --git a/tests/tooling/hooks.py b/src/py/reactpy/tests/tooling/hooks.py similarity index 100% rename from tests/tooling/hooks.py rename to src/py/reactpy/tests/tooling/hooks.py diff --git a/tests/tooling/loop.py b/src/py/reactpy/tests/tooling/loop.py similarity index 75% rename from tests/tooling/loop.py rename to src/py/reactpy/tests/tooling/loop.py index db6f91b83..f9e100981 100644 --- a/tests/tooling/loop.py +++ b/src/py/reactpy/tests/tooling/loop.py @@ -1,10 +1,9 @@ import asyncio -import sys import threading import time from asyncio import wait_for +from collections.abc import Iterator from contextlib import contextmanager -from typing import Iterator from reactpy.config import REACTPY_TESTING_DEFAULT_TIMEOUT @@ -32,24 +31,20 @@ def open_event_loop(as_current: bool = True) -> Iterator[asyncio.AbstractEventLo REACTPY_TESTING_DEFAULT_TIMEOUT.current, ) ) - if sys.version_info >= (3, 9): - # shutdown_default_executor only available in Python 3.9+ - loop.run_until_complete( - wait_for( - loop.shutdown_default_executor(), - REACTPY_TESTING_DEFAULT_TIMEOUT.current, - ) + loop.run_until_complete( + wait_for( + loop.shutdown_default_executor(), + REACTPY_TESTING_DEFAULT_TIMEOUT.current, ) + ) finally: if as_current: asyncio.set_event_loop(None) start = time.time() while loop.is_running(): if (time.time() - start) > REACTPY_TESTING_DEFAULT_TIMEOUT.current: - raise TimeoutError( - "Failed to stop loop after " - f"{REACTPY_TESTING_DEFAULT_TIMEOUT.current} seconds" - ) + msg = f"Failed to stop loop after {REACTPY_TESTING_DEFAULT_TIMEOUT.current} seconds" + raise TimeoutError(msg) time.sleep(0.1) loop.close() @@ -79,10 +74,9 @@ def one_task_finished(future): REACTPY_TESTING_DEFAULT_TIMEOUT.current, ) ) - else: - # user was responsible for cancelling all tasks - if not done.wait(timeout=3): - raise TimeoutError("Could not stop event loop in time") + elif not done.wait(timeout=3): # user was responsible for cancelling all tasks + msg = "Could not stop event loop in time" + raise TimeoutError(msg) for task in to_cancel: if task.cancelled(): diff --git a/tasks.py b/tasks.py new file mode 100644 index 000000000..0a0097c78 --- /dev/null +++ b/tasks.py @@ -0,0 +1,426 @@ +from __future__ import annotations + +import json +import logging +import os +import re +import sys +from dataclasses import dataclass +from pathlib import Path +from shutil import rmtree +from typing import TYPE_CHECKING, Any, Callable + +import semver +import toml +from invoke import task +from invoke.context import Context +from invoke.exceptions import Exit + +# --- Typing Preamble ------------------------------------------------------------------ + + +if TYPE_CHECKING: + # not available in typing module until Python 3.8 + # not available in typing module until Python 3.10 + from typing import Literal, Protocol, TypeAlias + + class ReleasePrepFunc(Protocol): + def __call__( + self, context: Context, package: PackageInfo + ) -> Callable[[bool], None]: + ... + + LanguageName: TypeAlias = "Literal['py', 'js']" + + +# --- Constants ------------------------------------------------------------------------ + + +log = logging.getLogger(__name__) +log.setLevel("INFO") +log_handler = logging.StreamHandler(sys.stdout) +log_handler.setFormatter(logging.Formatter("%(message)s")) +log.addHandler(log_handler) + + +# --- Constants ------------------------------------------------------------------------ + + +ROOT = Path(__file__).parent +DOCS_DIR = ROOT / "docs" +SRC_DIR = ROOT / "src" +JS_DIR = SRC_DIR / "js" +PY_DIR = SRC_DIR / "py" +PY_PROJECTS = [p for p in PY_DIR.iterdir() if (p / "pyproject.toml").exists()] +TAG_PATTERN = re.compile( + # start + r"^" + # package name + r"(?P[0-9a-zA-Z-@/]+)-" + # package version + r"v(?P[0-9][0-9a-zA-Z-\.\+]*)" + # end + r"$" +) + + +# --- Tasks ---------------------------------------------------------------------------- + + +@task +def env(context: Context): + """Install development environment""" + env_py(context) + env_js(context) + + +@task +def env_py(context: Context): + """Install Python development environment""" + for py_proj in PY_PROJECTS: + py_proj_toml = toml.load(py_proj / "pyproject.toml") + hatch_default_env = py_proj_toml["tool"]["hatch"]["envs"].get("default", {}) + hatch_default_features = hatch_default_env.get("features", []) + hatch_default_deps = hatch_default_env.get("dependencies", []) + with context.cd(py_proj): + context.run(f"pip install '.[{','.join(hatch_default_features)}]'") + context.run(f"pip install {' '.join(map(repr, hatch_default_deps))}") + + +@task +def env_js(context: Context): + """Install JS development environment""" + in_js( + context, + "npm ci", + "npm run build", + hide="out", + ) + + +@task +def lint_py(context: Context, fix: bool = False): + """Run linters and type checkers""" + if fix: + context.run("ruff --fix .") + else: + context.run("ruff .") + context.run("black --check --diff .") + in_py( + context, + f"flake8 --toml-config {ROOT / 'pyproject.toml'} .", + "hatch run lint:all", + ) + + +@task(pre=[env_js]) +def lint_js(context: Context, fix: bool = False): + """Run linters and type checkers""" + if fix: + in_js(context, "npm run fix:format") + else: + in_js(context, "npm run check:format") + in_js(context, "npm run check:types") + + +@task +def test_py(context: Context, no_cov: bool = False): + """Run test suites""" + in_py( + context, + f"hatch run {'test' if no_cov else 'cov'} --maxfail=3 --reruns=3", + ) + + +@task(pre=[env_js]) +def test_js(context: Context): + """Run test suites""" + in_js(context, "npm run check:tests") + + +@task(pre=[env_py]) +def test_docs(context: Context): + with context.cd(DOCS_DIR): + context.run("poetry install") + context.run( + "poetry run sphinx-build " + "-a " # re-write all output files + "-T " # show full tracebacks + "-W " # turn warnings into errors + "--keep-going " # complete the build, but still report warnings as errors + "-b doctest " + "source " + "build", + ) + context.run("poetry run sphinx-build -b doctest source build") + + context.run("docker build . --file ./docs/Dockerfile") + + +@task +def docs(context: Context, docker: bool = False): + """Build documentation""" + if docker: + _docker_docs(context) + else: + _live_docs(context) + + +def _docker_docs(context: Context) -> None: + context.run("docker build . --file ./docs/Dockerfile --tag reactpy-docs:latest") + context.run( + "docker run -it -p 5000:5000 -e DEBUG=1 --rm reactpy-docs:latest", pty=True + ) + + +def _live_docs(context: Context) -> None: + with context.cd(DOCS_DIR): + context.run("poetry install") + context.run( + "poetry run python main.py " + "--open-browser " + # watch python source too + "--watch=../src/py " + # for some reason this matches absolute paths + "--ignore=**/_auto/* " + "--ignore=**/_static/custom.js " + "--ignore=**/node_modules/* " + "--ignore=**/package-lock.json " + "-a " + "-E " + "-b " + "html " + "source " + "build" + ) + + +@task +def publish(context: Context, dry_run: str = ""): + """Publish packages that have been tagged for release in the current commit + + To perform a test run use `--dry-run=-v` to specify a comma-separated + list of tags to simulate a release of. For example, to simulate a release of + `@foo/bar-v1.2.3` and `baz-v4.5.6` use `--dry-run=@foo/bar-v1.2.3,baz-v4.5.6`. + """ + packages = get_packages(context) + + release_prep: dict[LanguageName, ReleasePrepFunc] = { + "js": prepare_js_release, + "py": prepare_py_release, + } + + parsed_tags: list[TagInfo] = [ + parse_tag(tag) for tag in dry_run.split(",") or get_current_tags(context) + ] + + publishers: list[Callable[[bool], None]] = [] + for tag_info in parsed_tags: + if tag_info.name not in packages: + msg = f"Tag {tag_info.tag} references package {tag_info.name} that does not exist" + raise Exit(msg) + + pkg_info = packages[tag_info.name] + if pkg_info.version != tag_info.version: + msg = f"Tag {tag_info.tag} references version {tag_info.version} of package {tag_info.name}, but the current version is {pkg_info.version}" + raise Exit(msg) + + log.info(f"Preparing {tag_info.name} for release...") + publishers.append(release_prep[pkg_info.language](context, pkg_info)) + + for publish in publishers: + publish(bool(dry_run)) + + +# --- Utilities ------------------------------------------------------------------------ + + +def in_py(context: Context, *commands: str, **kwargs: Any) -> None: + for p in PY_PROJECTS: + with context.cd(p): + log.info(f"Running commands in {p}...") + for c in commands: + context.run(c, **kwargs) + + +def in_js(context: Context, *commands: str, **kwargs: Any) -> None: + with context.cd(JS_DIR): + for c in commands: + context.run(c, **kwargs) + + +def get_packages(context: Context) -> dict[str, PackageInfo]: + packages: list[PackageInfo] = [] + + for maybe_pkg in PY_DIR.glob("*"): + if (maybe_pkg / "pyproject.toml").exists(): + packages.append(make_py_pkg_info(context, maybe_pkg)) + else: + msg = f"unexpected dir or file: {maybe_pkg}" + raise Exit(msg) + + packages_dir = JS_DIR / "packages" + for maybe_pkg in packages_dir.glob("*"): + if (maybe_pkg / "package.json").exists(): + packages.append(make_js_pkg_info(maybe_pkg)) + elif maybe_pkg.is_dir(): + for maybe_ns_pkg in maybe_pkg.glob("*"): + if (maybe_ns_pkg / "package.json").exists(): + packages.append(make_js_pkg_info(maybe_ns_pkg)) + else: + msg = f"unexpected dir or file: {maybe_pkg}" + raise Exit(msg) + + packages_by_name = {p.name: p for p in packages} + if len(packages_by_name) != len(packages): + raise Exit("duplicate package names detected") + + return packages_by_name + + +def make_py_pkg_info(context: Context, pkg_dir: Path) -> PackageInfo: + with context.cd(pkg_dir): + proj_metadata = json.loads(context.run("hatch project metadata").stdout) + return PackageInfo( + name=proj_metadata["name"], + path=pkg_dir, + language="py", + version=proj_metadata["version"], + ) + + +def make_js_pkg_info(pkg_dir: Path) -> PackageInfo: + with (pkg_dir / "package.json").open() as f: + pkg_json = json.load(f) + return PackageInfo( + name=pkg_json["name"], + path=pkg_dir, + language="js", + version=pkg_json["version"], + ) + + +@dataclass +class PackageInfo: + name: str + path: Path + language: LanguageName + version: str + + +def get_current_tags(context: Context) -> set[str]: + """Get tags for the current commit""" + # check if unstaged changes + try: + context.run("git diff --cached --exit-code", hide=True) + context.run("git diff --exit-code", hide=True) + except Exception: + log.error("Cannot create a tag - there are uncommited changes") + return set() + + tags_per_commit: dict[str, list[str]] = {} + for commit, tag in map( + str.split, + context.run( + r"git for-each-ref --format '%(objectname) %(refname:short)' refs/tags", + hide=True, + ).stdout.splitlines(), + ): + tags_per_commit.setdefault(commit, []).append(tag) + + current_commit = context.run( + "git rev-parse HEAD", silent=True, external=True + ).stdout.strip() + tags = set(tags_per_commit.get(current_commit, set())) + + if not tags: + log.error("No tags found for current commit") + + for t in tags: + if not TAG_PATTERN.match(t): + msg = f"Invalid tag: {t}" + raise Exit(msg) + + log.info(f"Found tags: {tags}") + + return tags + + +def parse_tag(tag: str) -> TagInfo: + match = TAG_PATTERN.match(tag) + if not match: + msg = f"Invalid tag: {tag}" + raise Exit(msg) + + version = match.group("version") + if not semver.Version.is_valid(version): + raise Exit(f"Invalid version: {version} in tag {tag}") + + return TagInfo(tag=tag, name=match.group("name"), version=match.group("version")) + + +@dataclass +class TagInfo: + tag: str + name: str + version: str + + +def prepare_js_release( + context: Context, package: PackageInfo +) -> Callable[[bool], None]: + node_auth_token = os.getenv("NODE_AUTH_TOKEN") + if node_auth_token is None: + msg = "NODE_AUTH_TOKEN environment variable must be set" + raise Exit(msg) + + with context.cd(JS_DIR): + context.run("npm ci") + context.run("npm run build") + + def publish(dry_run: bool) -> None: + with context.cd(JS_DIR): + if dry_run: + context.run(f"npm --workspace {package.name} pack --dry-run") + return + context.run( + f"npm --workspace {package.name} publish --access public", + env={"NODE_AUTH_TOKEN": node_auth_token}, + ) + + return publish + + +def prepare_py_release( + context: Context, package: PackageInfo +) -> Callable[[bool], None]: + twine_username = os.getenv("PYPI_USERNAME") + twine_password = os.getenv("PYPI_PASSWORD") + + if not (twine_password and twine_username): + msg = "PYPI_USERNAME and PYPI_PASSWORD environment variables must be set" + raise Exit(msg) + + for build_dir_name in ["build", "dist"]: + build_dir_path = Path.cwd() / build_dir_name + if build_dir_path.exists(): + rmtree(str(build_dir_path)) + + with context.cd(package.path): + context.run("hatch build") + + def publish(dry_run: bool): + with context.cd(package.path): + if dry_run: + context.run("twine check dist/*") + return + + context.run( + "twine upload dist/*", + env_dict={ + "TWINE_USERNAME": twine_username, + "TWINE_PASSWORD": twine_password, + }, + ) + + return publish diff --git a/tests/test_web/js_fixtures/export-resolution/index.js b/tests/test_web/js_fixtures/export-resolution/index.js deleted file mode 100644 index 2f1f46a51..000000000 --- a/tests/test_web/js_fixtures/export-resolution/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export {index as Index}; -export * from "./one.js";