From 93234751d67f7cee3dca1bdef0aa8ac7d1c810ab Mon Sep 17 00:00:00 2001 From: Alexander Sergeev <22302418+pseusys@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:52:34 +0200 Subject: [PATCH 01/11] `pepy` badge fix (#370) Pepy markdown badge was outdated. Now it is updated once again. --- .github/process_github_events.py | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/process_github_events.py b/.github/process_github_events.py index 5e94a71f7..3f4331aeb 100644 --- a/.github/process_github_events.py +++ b/.github/process_github_events.py @@ -48,6 +48,7 @@ def post_comment_on_pr(comment: str, pr_number: int): - [ ] Update package version - [ ] Update `poetry.lock` - [ ] Change PR merge option +- [ ] Update template repo - [ ] Search for objects to be deprecated """ diff --git a/README.md b/README.md index 1358f08bb..946d7feed 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![License Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/deeppavlov/chatsky/blob/master/LICENSE) ![Python 3.8, 3.9, 3.10, 3.11](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-green.svg) [![PyPI](https://img.shields.io/pypi/v/chatsky)](https://pypi.org/project/chatsky/) -[![Downloads](https://pepy.tech/badge/chatsky)](https://pepy.tech/project/chatsky) +[![Downloads](https://static.pepy.tech/badge/chatsky)](https://pepy.tech/project/chatsky) Chatsky allows you to develop conversational services. Chatsky offers a specialized domain-specific language (DSL) for quickly writing dialogs in pure Python. The service is created by defining a special dialog graph that determines the behavior of the dialog agent. The latter is then leveraged in the Chatsky pipeline. From ac8cda0a748bdb388aaba240f6e94240e6024a8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 17 Aug 2024 00:22:29 +0000 Subject: [PATCH 02/11] build(deps): bump the deps group across 1 directory with 17 updates (#380) Bumps the deps group with 17 updates in the / directory: | Package | From | To | | --- | --- | --- | | [pydantic](https://github.com/pydantic/pydantic) | `2.7.4` | `2.8.2` | | [ydb](https://github.com/ydb-platform/ydb-python-sdk) | `3.12.3` | `3.15.0` | | [altair](https://github.com/vega/altair) | `5.3.0` | `5.4.0` | | [pympler](https://github.com/pympler/pympler) | `1.0.1` | `1.1` | | [humanize](https://github.com/python-humanize/humanize) | `4.9.0` | `4.10.0` | | [cryptography](https://github.com/pyca/cryptography) | `42.0.8` | `43.0.0` | | [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) | `21.3` | `21.4` | | [opentelemetry-exporter-otlp](https://github.com/open-telemetry/opentelemetry-python) | `1.25.0` | `1.26.0` | | [black](https://github.com/psf/black) | `24.4.2` | `24.8.0` | | [mypy](https://github.com/python/mypy) | `1.10.1` | `1.11.1` | | [pytest](https://github.com/pytest-dev/pytest) | `8.2.2` | `8.3.2` | | [coverage](https://github.com/nedbat/coveragepy) | `7.5.4` | `7.6.1` | | [jsonschema](https://github.com/python-jsonschema/jsonschema) | `4.22.0` | `4.23.0` | | [python-on-whales](https://github.com/gabrieldemarmiesse/python-on-whales) | `0.71.0` | `0.72.0` | | [fastapi](https://github.com/fastapi/fastapi) | `0.111.0` | `0.112.0` | | [streamlit](https://github.com/streamlit/streamlit) | `1.36.0` | `1.37.1` | | [sphinx-gallery](https://github.com/sphinx-gallery/sphinx-gallery) | `0.16.0` | `0.17.1` | Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 1196 +++++++++++++++++++-------------------------------- 1 file changed, 435 insertions(+), 761 deletions(-) diff --git a/poetry.lock b/poetry.lock index eadc0df50..2f12d0b3e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -196,28 +196,26 @@ files = [ [[package]] name = "altair" -version = "5.3.0" +version = "5.4.0" description = "Vega-Altair: A declarative statistical visualization library for Python." optional = false python-versions = ">=3.8" files = [ - {file = "altair-5.3.0-py3-none-any.whl", hash = "sha256:7084a1dab4d83c5e7e5246b92dc1b4451a6c68fd057f3716ee9d315c8980e59a"}, - {file = "altair-5.3.0.tar.gz", hash = "sha256:5a268b1a0983b23d8f9129f819f956174aa7aea2719ed55a52eba9979b9f6675"}, + {file = "altair-5.4.0-py3-none-any.whl", hash = "sha256:86be974867007cfdf5c92d6f89926535546a4d00e0ea6c1745ef4d5937aad9df"}, + {file = "altair-5.4.0.tar.gz", hash = "sha256:27c69e93d85b7bb3c98fa3626ef7e6bc6939a1466a55a8f8bf68c4bff31cf030"}, ] [package.dependencies] jinja2 = "*" jsonschema = ">=3.0" -numpy = "*" +narwhals = ">=1.1.0" packaging = "*" -pandas = ">=0.25" -toolz = "*" -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] -all = ["altair-tiles (>=0.3.0)", "anywidget (>=0.9.0)", "pyarrow (>=11)", "vega-datasets (>=0.9.0)", "vegafusion[embed] (>=1.6.6)", "vl-convert-python (>=1.3.0)"] -dev = ["geopandas", "hatch", "ipython", "m2r", "mypy", "pandas-stubs", "pytest", "pytest-cov", "ruff (>=0.3.0)", "types-jsonschema", "types-setuptools"] -doc = ["docutils", "jinja2", "myst-parser", "numpydoc", "pillow (>=9,<10)", "pydata-sphinx-theme (>=0.14.1)", "scipy", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinxext-altair"] +all = ["altair-tiles (>=0.3.0)", "anywidget (>=0.9.0)", "numpy", "pandas (>=0.25.3)", "pyarrow (>=11)", "vega-datasets (>=0.9.0)", "vegafusion[embed] (>=1.6.6)", "vl-convert-python (>=1.6.0)"] +dev = ["geopandas", "hatch", "ibis-framework[polars]", "ipython[kernel]", "mistune", "mypy", "pandas (>=0.25.3)", "pandas-stubs", "polars (>=0.20.3)", "pytest", "pytest-cov", "pytest-xdist[psutil] (>=3.5,<4.0)", "ruff (>=0.5.7)", "types-jsonschema", "types-setuptools"] +doc = ["docutils", "jinja2", "myst-parser", "numpydoc", "pillow (>=9,<10)", "pydata-sphinx-theme (>=0.14.1)", "scipy", "sphinx (>=8.0.0)", "sphinx-copybutton", "sphinx-design", "sphinxext-altair"] [[package]] name = "annotated-types" @@ -661,33 +659,33 @@ lxml = ["lxml"] [[package]] name = "black" -version = "24.4.2" +version = "24.8.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, ] [package.dependencies] @@ -896,63 +894,78 @@ files = [ [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, + {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, + {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, + {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, + {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, + {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, + {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, + {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, + {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, + {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, + {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, + {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, + {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, + {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, + {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, ] [package.dependencies] @@ -1142,63 +1155,83 @@ files = [ [[package]] name = "coverage" -version = "7.5.4" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, - {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, - {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, - {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, - {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, - {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, - {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, - {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, - {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, - {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, - {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, - {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, - {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, - {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] @@ -1220,43 +1253,38 @@ files = [ [[package]] name = "cryptography" -version = "42.0.8" +version = "43.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, - {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, - {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, - {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, - {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, - {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, - {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, + {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, + {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, + {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, + {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, + {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, + {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, + {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, ] [package.dependencies] @@ -1269,7 +1297,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1357,7 +1385,7 @@ files = [ name = "dnspython" version = "2.6.1" description = "DNS toolkit" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, @@ -1471,21 +1499,6 @@ https = ["urllib3 (>=1.24.1)"] paramiko = ["paramiko"] pgp = ["gpg"] -[[package]] -name = "email-validator" -version = "2.2.0" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, - {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - [[package]] name = "eval-type-backport" version = "0.2.0" @@ -1544,47 +1557,23 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "fastapi" -version = "0.111.0" +version = "0.112.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.111.0-py3-none-any.whl", hash = "sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0"}, - {file = "fastapi-0.111.0.tar.gz", hash = "sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7"}, + {file = "fastapi-0.112.0-py3-none-any.whl", hash = "sha256:3487ded9778006a45834b8c816ec4a48d522e2631ca9e75ec5a774f1b052f821"}, + {file = "fastapi-0.112.0.tar.gz", hash = "sha256:d262bc56b7d101d1f4e8fc0ad2ac75bb9935fec504d2b7117686cec50710cf05"}, ] [package.dependencies] -email_validator = ">=2.0.0" -fastapi-cli = ">=0.0.2" -httpx = ">=0.23.0" -jinja2 = ">=2.11.2" -orjson = ">=3.2.1" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -python-multipart = ">=0.0.7" starlette = ">=0.37.2,<0.38.0" typing-extensions = ">=4.8.0" -ujson = ">=4.0.1,<4.0.2 || >4.0.2,<4.1.0 || >4.1.0,<4.2.0 || >4.2.0,<4.3.0 || >4.3.0,<5.0.0 || >5.0.0,<5.1.0 || >5.1.0" -uvicorn = {version = ">=0.12.0", extras = ["standard"]} [package.extras] -all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "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)"] - -[[package]] -name = "fastapi-cli" -version = "0.0.4" -description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"}, - {file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"}, -] - -[package.dependencies] -typer = ">=0.12.3" - -[package.extras] -standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"] +all = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "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)"] +standard = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "fastjsonschema" @@ -2207,54 +2196,6 @@ http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<0.26.0)"] -[[package]] -name = "httptools" -version = "0.6.1" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - [[package]] name = "httpx" version = "0.27.0" @@ -2283,13 +2224,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "humanize" -version = "4.9.0" +version = "4.10.0" description = "Python humanize utilities" optional = true python-versions = ">=3.8" files = [ - {file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"}, - {file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"}, + {file = "humanize-4.10.0-py3-none-any.whl", hash = "sha256:39e7ccb96923e732b5c2e27aeaa3b10a8dfeeba3eb965ba7b74a3eb0e30040a6"}, + {file = "humanize-4.10.0.tar.gz", hash = "sha256:06b6eb0293e4b85e8d385397c5868926820db32b9b654b932f57fa41c23c9978"}, ] [package.extras] @@ -2612,13 +2553,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.22.0" +version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, - {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, ] [package.dependencies] @@ -2635,11 +2576,11 @@ rfc3339-validator = {version = "*", optional = true, markers = "extra == \"forma rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} rpds-py = ">=0.7.1" uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} -webcolors = {version = ">=1.11", optional = true, markers = "extra == \"format-nongpl\""} +webcolors = {version = ">=24.6.0", optional = true, markers = "extra == \"format-nongpl\""} [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] [[package]] name = "jsonschema-specifications" @@ -3378,44 +3319,44 @@ files = [ [[package]] name = "mypy" -version = "1.10.1" +version = "1.11.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, - {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, - {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, - {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, - {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, - {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, - {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, - {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, - {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, - {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, - {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, - {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, - {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, - {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, - {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, - {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, - {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, - {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, - {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, + {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, + {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, + {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, + {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, + {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, + {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, + {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, + {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, + {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, + {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, + {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, + {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, + {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, + {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, + {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, + {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, + {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -3434,6 +3375,22 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "narwhals" +version = "1.3.0" +description = "Extremely lightweight compatibility layer between dataframe libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "narwhals-1.3.0-py3-none-any.whl", hash = "sha256:b8a0f191d5e94927cbbeb52dc3546272e59511f5c0e0733a2dd8e448933f193f"}, + {file = "narwhals-1.3.0.tar.gz", hash = "sha256:e30408c88c401de3c65a98edd7a444e181adfe7dec715cd9aa2899cc118e98a1"}, +] + +[package.extras] +pandas = ["pandas (>=0.25.3)"] +polars = ["polars (>=0.20.3)"] +pyarrow = ["pyarrow (>=11.0.0)"] + [[package]] name = "nbclient" version = "0.10.0" @@ -3639,57 +3596,57 @@ PyYAML = ">=5.1.0" [[package]] name = "opentelemetry-api" -version = "1.25.0" +version = "1.26.0" description = "OpenTelemetry Python API" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_api-1.25.0-py3-none-any.whl", hash = "sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737"}, - {file = "opentelemetry_api-1.25.0.tar.gz", hash = "sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869"}, + {file = "opentelemetry_api-1.26.0-py3-none-any.whl", hash = "sha256:7d7ea33adf2ceda2dd680b18b1677e4152000b37ca76e679da71ff103b943064"}, + {file = "opentelemetry_api-1.26.0.tar.gz", hash = "sha256:2bd639e4bed5b18486fef0b5a520aaffde5a18fc225e808a1ac4df363f43a1ce"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=7.1" +importlib-metadata = ">=6.0,<=8.0.0" [[package]] name = "opentelemetry-exporter-otlp" -version = "1.25.0" +version = "1.26.0" description = "OpenTelemetry Collector Exporters" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp-1.25.0-py3-none-any.whl", hash = "sha256:d67a831757014a3bc3174e4cd629ae1493b7ba8d189e8a007003cacb9f1a6b60"}, - {file = "opentelemetry_exporter_otlp-1.25.0.tar.gz", hash = "sha256:ce03199c1680a845f82e12c0a6a8f61036048c07ec7a0bd943142aca8fa6ced0"}, + {file = "opentelemetry_exporter_otlp-1.26.0-py3-none-any.whl", hash = "sha256:f839989f54bda85ee33c5dae033c44dcec9ccbb0dafc6a43d585df44da1d2036"}, + {file = "opentelemetry_exporter_otlp-1.26.0.tar.gz", hash = "sha256:cf0e093f080011951d9f97431a83869761e4d4ebe83a4195ee92d7806223299c"}, ] [package.dependencies] -opentelemetry-exporter-otlp-proto-grpc = "1.25.0" -opentelemetry-exporter-otlp-proto-http = "1.25.0" +opentelemetry-exporter-otlp-proto-grpc = "1.26.0" +opentelemetry-exporter-otlp-proto-http = "1.26.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.25.0" +version = "1.26.0" description = "OpenTelemetry Protobuf encoding" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.25.0-py3-none-any.whl", hash = "sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.25.0.tar.gz", hash = "sha256:c93f4e30da4eee02bacd1e004eb82ce4da143a2f8e15b987a9f603e0a85407d3"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.26.0-py3-none-any.whl", hash = "sha256:ee4d8f8891a1b9c372abf8d109409e5b81947cf66423fd998e56880057afbc71"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.26.0.tar.gz", hash = "sha256:bdbe50e2e22a1c71acaa0c8ba6efaadd58882e5a5978737a44a4c4b10d304c92"}, ] [package.dependencies] -opentelemetry-proto = "1.25.0" +opentelemetry-proto = "1.26.0" [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.25.0" +version = "1.26.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0-py3-none-any.whl", hash = "sha256:3131028f0c0a155a64c430ca600fd658e8e37043cb13209f0109db5c1a3e4eb4"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0.tar.gz", hash = "sha256:c0b1661415acec5af87625587efa1ccab68b873745ca0ee96b69bb1042087eac"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.26.0-py3-none-any.whl", hash = "sha256:e2be5eff72ebcb010675b818e8d7c2e7d61ec451755b8de67a140bc49b9b0280"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.26.0.tar.gz", hash = "sha256:a65b67a9a6b06ba1ec406114568e21afe88c1cdb29c464f2507d529eb906d8ae"}, ] [package.dependencies] @@ -3697,28 +3654,28 @@ deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" grpcio = ">=1.0.0,<2.0.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.25.0" -opentelemetry-proto = "1.25.0" -opentelemetry-sdk = ">=1.25.0,<1.26.0" +opentelemetry-exporter-otlp-proto-common = "1.26.0" +opentelemetry-proto = "1.26.0" +opentelemetry-sdk = ">=1.26.0,<1.27.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.25.0" +version = "1.26.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.25.0-py3-none-any.whl", hash = "sha256:2eca686ee11b27acd28198b3ea5e5863a53d1266b91cda47c839d95d5e0541a6"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.25.0.tar.gz", hash = "sha256:9f8723859e37c75183ea7afa73a3542f01d0fd274a5b97487ea24cb683d7d684"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.26.0-py3-none-any.whl", hash = "sha256:ee72a87c48ec977421b02f16c52ea8d884122470e0be573905237b540f4ee562"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.26.0.tar.gz", hash = "sha256:5801ebbcf7b527377883e6cbbdda35ee712dc55114fff1e93dfee210be56c908"}, ] [package.dependencies] deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.25.0" -opentelemetry-proto = "1.25.0" -opentelemetry-sdk = ">=1.25.0,<1.26.0" +opentelemetry-exporter-otlp-proto-common = "1.26.0" +opentelemetry-proto = "1.26.0" +opentelemetry-sdk = ">=1.26.0,<1.27.0" requests = ">=2.7,<3.0" [[package]] @@ -3739,13 +3696,13 @@ wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-proto" -version = "1.25.0" +version = "1.26.0" description = "OpenTelemetry Python Proto" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_proto-1.25.0-py3-none-any.whl", hash = "sha256:f07e3341c78d835d9b86665903b199893befa5e98866f63d22b00d0b7ca4972f"}, - {file = "opentelemetry_proto-1.25.0.tar.gz", hash = "sha256:35b6ef9dc4a9f7853ecc5006738ad40443701e52c26099e197895cbda8b815a3"}, + {file = "opentelemetry_proto-1.26.0-py3-none-any.whl", hash = "sha256:6c4d7b4d4d9c88543bcf8c28ae3f8f0448a753dc291c18c5390444c90b76a725"}, + {file = "opentelemetry_proto-1.26.0.tar.gz", hash = "sha256:c5c18796c0cab3751fc3b98dee53855835e90c0422924b484432ac852d93dc1e"}, ] [package.dependencies] @@ -3753,89 +3710,35 @@ protobuf = ">=3.19,<5.0" [[package]] name = "opentelemetry-sdk" -version = "1.25.0" +version = "1.26.0" description = "OpenTelemetry Python SDK" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_sdk-1.25.0-py3-none-any.whl", hash = "sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9"}, - {file = "opentelemetry_sdk-1.25.0.tar.gz", hash = "sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7"}, + {file = "opentelemetry_sdk-1.26.0-py3-none-any.whl", hash = "sha256:feb5056a84a88670c041ea0ded9921fca559efec03905dddeb3885525e0af897"}, + {file = "opentelemetry_sdk-1.26.0.tar.gz", hash = "sha256:c90d2868f8805619535c05562d699e2f4fb1f00dbd55a86dcefca4da6fa02f85"}, ] [package.dependencies] -opentelemetry-api = "1.25.0" -opentelemetry-semantic-conventions = "0.46b0" +opentelemetry-api = "1.26.0" +opentelemetry-semantic-conventions = "0.47b0" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.46b0" +version = "0.47b0" description = "OpenTelemetry Semantic Conventions" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl", hash = "sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07"}, - {file = "opentelemetry_semantic_conventions-0.46b0.tar.gz", hash = "sha256:fbc982ecbb6a6e90869b15c1673be90bd18c8a56ff1cffc0864e38e2edffaefa"}, -] - -[package.dependencies] -opentelemetry-api = "1.25.0" - -[[package]] -name = "orjson" -version = "3.10.5" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c"}, - {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96"}, - {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b"}, - {file = "orjson-3.10.5-cp310-none-win32.whl", hash = "sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2"}, - {file = "orjson-3.10.5-cp310-none-win_amd64.whl", hash = "sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228"}, - {file = "orjson-3.10.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5"}, - {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f"}, - {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa"}, - {file = "orjson-3.10.5-cp311-none-win32.whl", hash = "sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04"}, - {file = "orjson-3.10.5-cp311-none-win_amd64.whl", hash = "sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c"}, - {file = "orjson-3.10.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b"}, - {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211"}, - {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3"}, - {file = "orjson-3.10.5-cp312-none-win32.whl", hash = "sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2"}, - {file = "orjson-3.10.5-cp312-none-win_amd64.whl", hash = "sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5"}, - {file = "orjson-3.10.5-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e"}, - {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9"}, - {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b"}, - {file = "orjson-3.10.5-cp38-none-win32.whl", hash = "sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4"}, - {file = "orjson-3.10.5-cp38-none-win_amd64.whl", hash = "sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09"}, - {file = "orjson-3.10.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214"}, - {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595"}, - {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86"}, - {file = "orjson-3.10.5-cp39-none-win32.whl", hash = "sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47"}, - {file = "orjson-3.10.5-cp39-none-win_amd64.whl", hash = "sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7"}, - {file = "orjson-3.10.5.tar.gz", hash = "sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d"}, + {file = "opentelemetry_semantic_conventions-0.47b0-py3-none-any.whl", hash = "sha256:4ff9d595b85a59c1c1413f02bba320ce7ea6bf9e2ead2b0913c4395c7bbc1063"}, + {file = "opentelemetry_semantic_conventions-0.47b0.tar.gz", hash = "sha256:a8d57999bbe3495ffd4d510de26a97dadc1dace53e0275001b2c1b2f67992a7e"}, ] +[package.dependencies] +deprecated = ">=1.2.6" +opentelemetry-api = "1.26.0" + [[package]] name = "overrides" version = "7.7.0" @@ -4461,109 +4364,122 @@ files = [ [[package]] name = "pydantic" -version = "2.7.4" +version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, - {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.4" -typing-extensions = ">=4.6.1" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, +] [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.4" +version = "2.20.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, - {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, - {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, - {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, - {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, - {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, - {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, - {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, - {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, - {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, - {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, - {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, - {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, - {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, ] [package.dependencies] @@ -4714,15 +4630,18 @@ zstd = ["zstandard"] [[package]] name = "pympler" -version = "1.0.1" +version = "1.1" description = "A development tool to measure, monitor and analyze the memory behavior of Python objects." optional = true python-versions = ">=3.6" files = [ - {file = "Pympler-1.0.1-py3-none-any.whl", hash = "sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d"}, - {file = "Pympler-1.0.1.tar.gz", hash = "sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa"}, + {file = "Pympler-1.1-py3-none-any.whl", hash = "sha256:5b223d6027d0619584116a0cbc28e8d2e378f7a79c1e5e024f9ff3b673c58506"}, + {file = "pympler-1.1.tar.gz", hash = "sha256:1eaa867cb8992c218430f1708fdaccda53df064144d1c5656b1e6f1ee6000424"}, ] +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + [[package]] name = "pyproject-hooks" version = "1.1.0" @@ -4736,13 +4655,13 @@ files = [ [[package]] name = "pytest" -version = "8.2.2" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, - {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] @@ -4750,7 +4669,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -4891,29 +4810,15 @@ files = [ {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, ] -[[package]] -name = "python-multipart" -version = "0.0.9" -description = "A streaming multipart parser for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, - {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, -] - -[package.extras] -dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] - [[package]] name = "python-on-whales" -version = "0.71.0" +version = "0.72.0" description = "A Docker client for Python, designed to be fun and intuitive!" optional = false python-versions = "<4,>=3.8" files = [ - {file = "python_on_whales-0.71.0-py3-none-any.whl", hash = "sha256:9d23c025e2e887f8336fbdd324ce764e72e60f7db2d0599601e8f6ddac1cae2d"}, - {file = "python_on_whales-0.71.0.tar.gz", hash = "sha256:0967be1b716f4a40e44a4b3bf091f721b494205425c1215f64a1a612eb932900"}, + {file = "python_on_whales-0.72.0-py3-none-any.whl", hash = "sha256:092b440f43a34bbe90536b82a41ae3c199018dfc66d7e0dc52d4d659b82683c2"}, + {file = "python_on_whales-0.72.0.tar.gz", hash = "sha256:280da91724ae728ac2baf503584938ecfdee2f83bb88e65413eed14b1fe74d41"}, ] [package.dependencies] @@ -4928,35 +4833,36 @@ test = ["pytest"] [[package]] name = "python-telegram-bot" -version = "21.3" +version = "21.4" description = "We have made you a wrapper you can't refuse" optional = true python-versions = ">=3.8" files = [ - {file = "python-telegram-bot-21.3.tar.gz", hash = "sha256:1be3c8b6f2b7354418109daa3f23c522e82ed22e7fc904346bee0c7b4aab52ae"}, - {file = "python_telegram_bot-21.3-py3-none-any.whl", hash = "sha256:8f575e6da903edd1e78967b5b481455ee6b27f2804d2384029177eab165f2e93"}, + {file = "python_telegram_bot-21.4-py3-none-any.whl", hash = "sha256:71ce864130af43b48be70ff0593ef4db31d99d92aa64a2933289b8d544e6e9df"}, + {file = "python_telegram_bot-21.4.tar.gz", hash = "sha256:d4d41f29f2e2c02920a4be75d9c1ecf85ec381bf47bbeb51af5b097e340b8377"}, ] [package.dependencies] aiolimiter = {version = ">=1.1.0,<1.2.0", optional = true, markers = "extra == \"all\""} -APScheduler = {version = ">=3.10.4,<3.11.0", optional = true, markers = "extra == \"all\""} +apscheduler = {version = ">=3.10.4,<3.11.0", optional = true, markers = "extra == \"all\""} cachetools = {version = ">=5.3.3,<5.4.0", optional = true, markers = "extra == \"all\""} +cffi = {version = ">=1.17.0rc1", optional = true, markers = "python_version > \"3.12\" and extra == \"all\""} cryptography = {version = ">=39.0.1", optional = true, markers = "extra == \"all\""} httpx = [ {version = ">=0.27,<1.0"}, - {version = "*", extras = ["socks"], optional = true, markers = "extra == \"all\""}, {version = "*", extras = ["http2"], optional = true, markers = "extra == \"all\""}, + {version = "*", extras = ["socks"], optional = true, markers = "extra == \"all\""}, ] pytz = {version = ">=2018.6", optional = true, markers = "extra == \"all\""} tornado = {version = ">=6.4,<7.0", optional = true, markers = "extra == \"all\""} [package.extras] -all = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.3,<5.4.0)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +all = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.4.0)", "cffi (>=1.17.0rc1)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] callback-data = ["cachetools (>=5.3.3,<5.4.0)"] -ext = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.3,<5.4.0)", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +ext = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.4.0)", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] http2 = ["httpx[http2]"] -job-queue = ["APScheduler (>=3.10.4,<3.11.0)", "pytz (>=2018.6)"] -passport = ["cryptography (>=39.0.1)"] +job-queue = ["apscheduler (>=3.10.4,<3.11.0)", "pytz (>=2018.6)"] +passport = ["cffi (>=1.17.0rc1)", "cryptography (>=39.0.1)"] rate-limiter = ["aiolimiter (>=1.1.0,<1.2.0)"] socks = ["httpx[socks]"] webhooks = ["tornado (>=6.4,<7.0)"] @@ -5046,6 +4952,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -5787,21 +5694,24 @@ test = ["beautifulsoup4", "pytest", "pytest-cov"] [[package]] name = "sphinx-gallery" -version = "0.16.0" +version = "0.17.1" description = "A Sphinx extension that builds an HTML gallery of examples from any set of Python scripts." optional = false python-versions = ">=3.8" files = [ - {file = "sphinx_gallery-0.16.0-py3-none-any.whl", hash = "sha256:f5456514f4efb230a6f1db6241667774ca3ee8f15e9a7456678f1d1815118e60"}, - {file = "sphinx_gallery-0.16.0.tar.gz", hash = "sha256:3912765bc5e7b5451dc471ad50ead808a9752280b23fd2ec4277719a5ef68e42"}, + {file = "sphinx_gallery-0.17.1-py3-none-any.whl", hash = "sha256:0a1142a15a9d63169fe7b12167dc028891fb8db31bfc6d7de03ba0d68d591830"}, + {file = "sphinx_gallery-0.17.1.tar.gz", hash = "sha256:c9969abcc5ca8c24496014da8260833b8c3ccdb32c17716b5ba66f2e0a3cc183"}, ] [package.dependencies] pillow = "*" -sphinx = ">=4" +sphinx = ">=5" [package.extras] +animations = ["sphinxcontrib-video"] +dev = ["absl-py", "graphviz", "intersphinx-registry", "ipython", "joblib", "jupyterlite-sphinx", "lxml", "matplotlib", "numpy", "packaging", "plotly", "pydata-sphinx-theme", "pytest", "pytest-coverage", "seaborn", "sphinxcontrib-video", "statsmodels"] jupyterlite = ["jupyterlite-sphinx"] +parallel = ["joblib"] recommender = ["numpy"] show-api-usage = ["graphviz"] show-memory = ["memory-profiler"] @@ -6080,13 +5990,13 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "streamlit" -version = "1.36.0" +version = "1.37.1" description = "A faster way to build and share data apps" optional = false python-versions = "!=3.9.7,>=3.8" files = [ - {file = "streamlit-1.36.0-py2.py3-none-any.whl", hash = "sha256:3399a33ea5faa26c05dd433d142eefe68ade67e9189a9e1d47a1731ae30a1c42"}, - {file = "streamlit-1.36.0.tar.gz", hash = "sha256:a12af9f0eb61ab5832f438336257b1ec20eb29d8e0e0c6b40a79116ba939bc9c"}, + {file = "streamlit-1.37.1-py2.py3-none-any.whl", hash = "sha256:0651240fccc569900cc9450390b0a67473fda55be65f317e46285f99e2bddf04"}, + {file = "streamlit-1.37.1.tar.gz", hash = "sha256:bc7e3813d94a39dda56f15678437eb37830973c601e8e574f2225a7bf188ea5a"}, ] [package.dependencies] @@ -6245,17 +6155,6 @@ files = [ {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, ] -[[package]] -name = "toolz" -version = "0.12.1" -description = "List processing tools and functional utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, - {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, -] - [[package]] name = "tornado" version = "6.4.1" @@ -6390,93 +6289,6 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] -[[package]] -name = "ujson" -version = "5.10.0" -description = "Ultra fast JSON encoder and decoder for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, - {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"}, - {file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"}, - {file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"}, - {file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"}, - {file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"}, - {file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"}, - {file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"}, - {file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"}, - {file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"}, - {file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"}, - {file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"}, - {file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"}, - {file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"}, - {file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"}, - {file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"}, - {file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"}, - {file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"}, - {file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"}, - {file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"}, - {file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"}, - {file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"}, - {file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"}, - {file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"}, - {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"}, -] - [[package]] name = "uri-template" version = "1.3.0" @@ -6520,63 +6332,12 @@ files = [ [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\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -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.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -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.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - [[package]] name = "virtualenv" version = "20.26.3" @@ -6641,93 +6402,6 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] -[[package]] -name = "watchfiles" -version = "0.22.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, - {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"}, - {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"}, - {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"}, - {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"}, - {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"}, - {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"}, - {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"}, - {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"}, - {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"}, - {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"}, - {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"}, - {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"}, - {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"}, - {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - [[package]] name = "wcwidth" version = "0.2.13" @@ -7147,13 +6821,13 @@ multidict = ">=4.0" [[package]] name = "ydb" -version = "3.12.3" +version = "3.15.0" description = "YDB Python SDK" optional = true python-versions = "*" files = [ - {file = "ydb-3.12.3-py2.py3-none-any.whl", hash = "sha256:0bb1094d471c47c3da773dc607ae47129899becdcca5756d199e343140599799"}, - {file = "ydb-3.12.3.tar.gz", hash = "sha256:6895e97218d464cb6e46fedebb8e855e385740e61bd700fd26983c9daeb9ba74"}, + {file = "ydb-3.15.0-py2.py3-none-any.whl", hash = "sha256:0eacc4ffa9dbea05464a3118770840efd6d9aca3a445693be22648e3c5124a2a"}, + {file = "ydb-3.15.0.tar.gz", hash = "sha256:84597f9ad78e07d55923a753fc409a17622e1bece8b4ee7192edfde5dc5146fc"}, ] [package.dependencies] From 385f0a54470983eebc152a5d9940c97505159a47 Mon Sep 17 00:00:00 2001 From: ZergLev <64711614+ZergLev@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:27:47 +0300 Subject: [PATCH 03/11] Pydantic improvements (#372) # Description: - TLDR: All classes in /chatsky/pipeline/ are rewritten in Pydantic, some changes to class hierarchy and the classes themselves. Changed tutorials with regards to new class signatures. - Classes `PipelineComponent`, `Pipeline` and `ComponentExtraHandler` are now subclasses of Pydantic's `BaseModel`, massively reducing code bloat in their initialization and that of their children (such as `Service`). - Also, classes `Service`, `ServiceGroup` and `Actor` are now subclasses of `PipelineComponent`. No functionality is removed in `ServiceGroup`. All tutorials and tests pass like before, with a few changes. - Reasoning for changing class hierarchy: Before this, an instance of `Actor` would be called from within a `Service` with a special flag, which seemed like a workaround, and now it can be called directly as an instance of `PipelineComponent`, instead of heavily relying on the `Service` class. You could say that this slightly reduces the code's complexity. In fact, it also binds `Actor` to `PipelineComponent`, instead of it being independent, making code look more cohesive. - TypeBuilders have been removed, because Pydantic can already deal with them effectively, giving type hints for IDE like before. Different input types are still handled like before, except nested `Service` declaration is removed. (`Service` class doesn't accept `Service` as it's handler anymore) Same with nested `ComponentExtraHandler`s. - For some reason Extra Handlers in `Service`s would work even if the Service itself was turned off via `start_condition`. That was a bug. Now this condition is checked in `PipelineComponent` instead. I assume `Actor` must always have it as True. # Checklist - [x] Removed `from_script()` and other redundant methods. - [x] Changed `components` into `pre-services` and `post-services` throughout tutorials. (at `Pipeline` initialization) - [x] Got tests to pass. - [x] I have performed a self-review of the changes - [x] Updated Pipeline's API and tutorials # To Consider - Add more tests if necessary - Update the remaining API reference / tutorials / guides - Search for more references to changed entities in the codebase --------- Co-authored-by: Roman Zlobin --- chatsky/__rebuild_pydantic_models__.py | 3 +- chatsky/pipeline/__init__.py | 13 +- chatsky/pipeline/pipeline/actor.py | 132 ++++--- chatsky/pipeline/pipeline/component.py | 153 ++++---- chatsky/pipeline/pipeline/pipeline.py | 328 +++++++----------- chatsky/pipeline/pipeline/utils.py | 95 +---- chatsky/pipeline/service/extra.py | 115 +++--- chatsky/pipeline/service/group.py | 176 ++++------ chatsky/pipeline/service/service.py | 173 +++------ chatsky/pipeline/service/utils.py | 53 --- chatsky/pipeline/types.py | 97 +----- chatsky/utils/testing/__init__.py | 2 +- chatsky/utils/testing/toy_script.py | 11 +- tests/context_storages/test_dbs.py | 4 +- tests/pipeline/test_messenger_interface.py | 4 +- tests/pipeline/test_parallel_processing.py | 4 +- tests/pipeline/test_pipeline.py | 20 +- tests/pipeline/test_update_ctx_misc.py | 2 +- tests/pipeline/test_validation.py | 182 ++++++++++ tests/script/conditions/test_conditions.py | 2 +- tests/script/core/test_actor.py | 31 +- tests/script/core/test_normalization.py | 2 +- tests/script/labels/test_labels.py | 4 +- tests/script/responses/test_responses.py | 2 +- tests/slots/conftest.py | 2 +- tests/stats/test_defaults.py | 2 +- tutorials/context_storages/1_basics.py | 4 +- tutorials/context_storages/2_postgresql.py | 4 +- tutorials/context_storages/3_mongodb.py | 4 +- tutorials/context_storages/4_redis.py | 4 +- tutorials/context_storages/5_mysql.py | 4 +- tutorials/context_storages/6_sqlite.py | 4 +- .../context_storages/7_yandex_database.py | 4 +- tutorials/messengers/telegram/1_basic.py | 2 +- .../messengers/telegram/2_attachments.py | 2 +- tutorials/messengers/telegram/3_advanced.py | 2 +- .../messengers/web_api_interface/1_fastapi.py | 6 +- .../web_api_interface/2_websocket_chat.py | 6 +- tutorials/pipeline/1_basics.py | 24 +- .../pipeline/2_pre_and_post_processors.py | 16 +- .../3_pipeline_dict_with_services_basic.py | 64 ++-- .../3_pipeline_dict_with_services_full.py | 76 ++-- .../pipeline/4_groups_and_conditions_basic.py | 21 +- .../pipeline/4_groups_and_conditions_full.py | 52 +-- ..._asynchronous_groups_and_services_basic.py | 9 +- ...5_asynchronous_groups_and_services_full.py | 39 +-- tutorials/pipeline/6_extra_handlers_basic.py | 72 ++-- tutorials/pipeline/6_extra_handlers_full.py | 23 +- .../7_extra_handlers_and_extensions.py | 9 +- tutorials/script/core/1_basics.py | 6 +- tutorials/script/core/2_conditions.py | 4 +- tutorials/script/core/3_responses.py | 4 +- tutorials/script/core/4_transitions.py | 4 +- tutorials/script/core/5_global_transitions.py | 4 +- .../script/core/6_context_serialization.py | 4 +- .../script/core/7_pre_response_processing.py | 4 +- tutorials/script/core/8_misc.py | 4 +- .../core/9_pre_transitions_processing.py | 4 +- tutorials/script/responses/1_basics.py | 4 +- tutorials/script/responses/2_media.py | 4 +- tutorials/script/responses/3_multi_message.py | 4 +- tutorials/slots/1_basic_example.py | 4 +- tutorials/stats/1_extractor_functions.py | 22 +- tutorials/stats/2_pipeline_integration.py | 60 ++-- tutorials/utils/1_cache.py | 2 +- tutorials/utils/2_lru_cache.py | 2 +- utils/stats/sample_data_provider.py | 29 +- 67 files changed, 1000 insertions(+), 1232 deletions(-) delete mode 100644 chatsky/pipeline/service/utils.py create mode 100644 tests/pipeline/test_validation.py diff --git a/chatsky/__rebuild_pydantic_models__.py b/chatsky/__rebuild_pydantic_models__.py index 6d4c5dd92..f648d6449 100644 --- a/chatsky/__rebuild_pydantic_models__.py +++ b/chatsky/__rebuild_pydantic_models__.py @@ -1,9 +1,10 @@ # flake8: noqa: F401 from chatsky.pipeline import Pipeline -from chatsky.pipeline.types import ExtraHandlerRuntimeInfo +from chatsky.pipeline.types import ExtraHandlerRuntimeInfo, StartConditionCheckerFunction from chatsky.script import Context, Script +Pipeline.model_rebuild() Script.model_rebuild() Context.model_rebuild() ExtraHandlerRuntimeInfo.model_rebuild() diff --git a/chatsky/pipeline/__init__.py b/chatsky/pipeline/__init__.py index 4fbe2286f..c6152f31c 100644 --- a/chatsky/pipeline/__init__.py +++ b/chatsky/pipeline/__init__.py @@ -20,14 +20,11 @@ ExtraHandlerRuntimeInfo, ExtraHandlerFunction, ServiceFunction, - ExtraHandlerBuilder, - ServiceBuilder, - ServiceGroupBuilder, - PipelineBuilder, ) -from .pipeline.pipeline import Pipeline, ACTOR - -from .service.extra import BeforeHandler, AfterHandler -from .service.group import ServiceGroup +from .service.extra import BeforeHandler, AfterHandler, ComponentExtraHandler from .service.service import Service, to_service +from .service.group import ServiceGroup + +from .pipeline.actor import Actor +from .pipeline.pipeline import Pipeline diff --git a/chatsky/pipeline/pipeline/actor.py b/chatsky/pipeline/pipeline/actor.py index 6f0256885..20800d6b2 100644 --- a/chatsky/pipeline/pipeline/actor.py +++ b/chatsky/pipeline/pipeline/actor.py @@ -27,8 +27,10 @@ import logging import asyncio from typing import Union, Callable, Optional, Dict, List, TYPE_CHECKING +from pydantic import Field, model_validator import copy +from chatsky.pipeline.pipeline.component import PipelineComponent from chatsky.utils.turn_caching import cache_clear from chatsky.script.core.types import ActorStage, NodeLabel2Type, NodeLabel3Type, LabelType from chatsky.script.core.message import Message @@ -45,58 +47,101 @@ from chatsky.pipeline.pipeline.pipeline import Pipeline -class Actor: +# Had to define this earlier, because when Pydantic starts it's __init__ it thinks of this function as +# being referenced before assignment +async def default_condition_handler( + condition: Callable, ctx: Context, pipeline: Pipeline +) -> Callable[[Context, Pipeline], bool]: + """ + The simplest and quickest condition handler for trivial condition handling returns the callable condition: + + :param condition: Condition to copy. + :param ctx: Context of current condition. + :param pipeline: Pipeline we use in this condition. + """ + return await wrap_sync_function_in_async(condition, ctx, pipeline) + + +class Actor(PipelineComponent): """ The class which is used to process :py:class:`~chatsky.script.Context` according to the :py:class:`~chatsky.script.Script`. + """ + + script: Union[Script, Dict] + """ + The dialog scenario: a graph described by the :py:class:`.Keywords`. + While the graph is being initialized, it is validated and then used for the dialog. + """ + start_label: NodeLabel2Type + """ + The start node of :py:class:`~chatsky.script.Script`. The execution begins with it. + """ + fallback_label: Optional[NodeLabel2Type] = None + """ + The label of :py:class:`~chatsky.script.Script`. + Dialog comes into that label if all other transitions failed, + or there was an error while executing the scenario. Defaults to `None`. + """ + label_priority: float = 1.0 + """ + Default priority value for all :py:const:`labels ` + where there is no priority. Defaults to `1.0`. + """ + condition_handler: Callable = Field(default=default_condition_handler) + """ + Handler that processes a call of condition functions. Defaults to `None`. + """ + handlers: Dict[ActorStage, List[Callable]] = Field(default_factory=dict) + """ + This variable is responsible for the usage of external handlers on + the certain stages of work of :py:class:`~chatsky.script.Actor`. + + - key (:py:class:`~chatsky.script.ActorStage`) - Stage in which the handler is called. + - value (`List[Callable]`) - The list of called handlers for each stage. Defaults to an empty `dict`. - :param script: The dialog scenario: a graph described by the :py:class:`.Keywords`. - While the graph is being initialized, it is validated and then used for the dialog. - :param start_label: The start node of :py:class:`~chatsky.script.Script`. The execution begins with it. - :param fallback_label: The label of :py:class:`~chatsky.script.Script`. - Dialog comes into that label if all other transitions failed, - or there was an error while executing the scenario. - Defaults to `None`. - :param label_priority: Default priority value for all :py:const:`labels ` - where there is no priority. Defaults to `1.0`. - :param condition_handler: Handler that processes a call of condition functions. Defaults to `None`. - :param handlers: This variable is responsible for the usage of external handlers on - the certain stages of work of :py:class:`~chatsky.script.Actor`. - - - key (:py:class:`~chatsky.script.ActorStage`) - Stage in which the handler is called. - - value (List[Callable]) - The list of called handlers for each stage. Defaults to an empty `dict`. """ + # NB! The following API is highly experimental and may be removed at ANY time WITHOUT FURTHER NOTICE!! + _clean_turn_cache: bool = True - def __init__( - self, - script: Union[Script, dict], - start_label: NodeLabel2Type, - fallback_label: Optional[NodeLabel2Type] = None, - label_priority: float = 1.0, - condition_handler: Optional[Callable] = None, - handlers: Optional[Dict[ActorStage, List[Callable]]] = None, - ): - self.script = script if isinstance(script, Script) else Script(script=script) - self.label_priority = label_priority - - self.start_label = normalize_label(start_label) + @model_validator(mode="after") + def __tick_async_flag__(self): + self.calculated_async_flag = False + return self + + @model_validator(mode="after") + def __start_label_validator__(self): + """ + Validate :py:data:`~.Actor.start_label`. + + :raises ValueError: If `start_label` doesn't exist in the given :py:class:`~.Script`. + """ + if not isinstance(self.script, Script): + self.script = Script(script=self.script) + self.start_label = normalize_label(self.start_label) if self.script.get(self.start_label[0], {}).get(self.start_label[1]) is None: raise ValueError(f"Unknown start_label={self.start_label}") + return self - if fallback_label is None: + @model_validator(mode="after") + def __fallback_label_validator__(self): + """ + Validate :py:data:`~.Actor.fallback_label`. + :raises ValueError: If `fallback_label` doesn't exist in the given :py:class:`~.Script`. + """ + if self.fallback_label is None: self.fallback_label = self.start_label else: - self.fallback_label = normalize_label(fallback_label) + self.fallback_label = normalize_label(self.fallback_label) if self.script.get(self.fallback_label[0], {}).get(self.fallback_label[1]) is None: raise ValueError(f"Unknown fallback_label={self.fallback_label}") - self.condition_handler = default_condition_handler if condition_handler is None else condition_handler - - self.handlers = {} if handlers is None else handlers + return self - # NB! The following API is highly experimental and may be removed at ANY time WITHOUT FURTHER NOTICE!! - self._clean_turn_cache = True + @property + def computed_name(self) -> str: + return "actor" - async def __call__(self, pipeline: Pipeline, ctx: Context): + async def run_component(self, ctx: Context, pipeline: Pipeline) -> None: await self._run_handlers(ctx, pipeline, ActorStage.CONTEXT_INIT) # get previous node @@ -364,16 +409,3 @@ def _choose_label( else: chosen_label = self.fallback_label return chosen_label - - -async def default_condition_handler( - condition: Callable, ctx: Context, pipeline: Pipeline -) -> Callable[[Context, Pipeline], bool]: - """ - The simplest and quickest condition handler for trivial condition handling returns the callable condition: - - :param condition: Condition to copy. - :param ctx: Context of current condition. - :param pipeline: Pipeline we use in this condition. - """ - return await wrap_sync_function_in_async(condition, ctx, pipeline) diff --git a/chatsky/pipeline/pipeline/component.py b/chatsky/pipeline/pipeline/component.py index ab37426ea..362530432 100644 --- a/chatsky/pipeline/pipeline/component.py +++ b/chatsky/pipeline/pipeline/component.py @@ -14,6 +14,7 @@ import abc import asyncio from typing import Optional, Awaitable, TYPE_CHECKING +from pydantic import BaseModel, Field, model_validator from chatsky.script import Context @@ -26,8 +27,8 @@ GlobalExtraHandlerType, ExtraHandlerFunction, ExtraHandlerType, - ExtraHandlerBuilder, ) +from ...utils.devel import wrap_sync_function_in_async logger = logging.getLogger(__name__) @@ -35,75 +36,70 @@ from chatsky.pipeline.pipeline.pipeline import Pipeline -class PipelineComponent(abc.ABC): +class PipelineComponent(abc.ABC, BaseModel, extra="forbid", arbitrary_types_allowed=True): """ This class represents a pipeline component, which is a service or a service group. It contains some fields that they have in common. + """ + + before_handler: BeforeHandler = Field(default_factory=BeforeHandler) + """ + :py:class:`~.BeforeHandler`, associated with this component. + """ + after_handler: AfterHandler = Field(default_factory=AfterHandler) + """ + :py:class:`~.AfterHandler`, associated with this component. + """ + timeout: Optional[float] = None + """ + (for asynchronous only!) Maximum component execution time (in seconds), + if it exceeds this time, it is interrupted. + """ + requested_async_flag: Optional[bool] = None + """ + Requested asynchronous property; if not defined, + :py:attr:`~.PipelineComponent.calculated_async_flag` is used instead. + """ + calculated_async_flag: bool = False + """ + Whether the component can be asynchronous or not. - :param before_handler: :py:class:`~.BeforeHandler`, associated with this component. - :type before_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param after_handler: :py:class:`~.AfterHandler`, associated with this component. - :type after_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param timeout: (for asynchronous only!) Maximum component execution time (in seconds), - if it exceeds this time, it is interrupted. - :param requested_async_flag: Requested asynchronous property; - if not defined, `calculated_async_flag` is used instead. - :param calculated_async_flag: Whether the component can be asynchronous or not - 1) for :py:class:`~.pipeline.service.service.Service`: whether its `handler` is asynchronous or not, - 2) for :py:class:`~.pipeline.service.group.ServiceGroup`: whether all its `services` are asynchronous or not. + 1) for :py:class:`~.pipeline.service.service.Service`: whether its `handler` is asynchronous or not, + 2) for :py:class:`~.pipeline.service.group.ServiceGroup`: whether all its `services` are asynchronous or not. - :param start_condition: StartConditionCheckerFunction that is invoked before each component execution; - component is executed only if it returns `True`. - :type start_condition: Optional[:py:data:`~.StartConditionCheckerFunction`] - :param name: Component name (should be unique in single :py:class:`~.pipeline.service.group.ServiceGroup`), - should not be blank or contain `.` symbol. - :param path: Separated by dots path to component, is universally unique. + """ + start_condition: StartConditionCheckerFunction = Field(default=always_start_condition) + """ + :py:class:`~.pipeline.types.StartConditionCheckerFunction` that is invoked before each component execution; + component is executed only if it returns `True`. + """ + name: Optional[str] = None + """ + Component name (should be unique in single :py:class:`~.pipeline.service.group.ServiceGroup`), + should not be blank or contain the ``.`` character. + """ + path: Optional[str] = None + """ + Separated by dots path to component, is universally unique. """ - def __init__( - self, - before_handler: Optional[ExtraHandlerBuilder] = None, - after_handler: Optional[ExtraHandlerBuilder] = None, - timeout: Optional[float] = None, - requested_async_flag: Optional[bool] = None, - calculated_async_flag: bool = False, - start_condition: Optional[StartConditionCheckerFunction] = None, - name: Optional[str] = None, - path: Optional[str] = None, - ): - self.timeout = timeout - """ - Maximum component execution time (in seconds), - if it exceeds this time, it is interrupted (for asynchronous only!). - """ - self.requested_async_flag = requested_async_flag - """Requested asynchronous property; if not defined, :py:attr:`~requested_async_flag` is used instead.""" - self.calculated_async_flag = calculated_async_flag - """Calculated asynchronous property, whether the component can be asynchronous or not.""" - self.start_condition = always_start_condition if start_condition is None else start_condition - """ - Component start condition that is invoked before each component execution; - component is executed only if it returns `True`. + @model_validator(mode="after") + def __pipeline_component_validator__(self): """ - self.name = name - """ - Component name (should be unique in single :py:class:`~pipeline.service.group.ServiceGroup`), - should not be blank or contain '.' symbol. - """ - self.path = path - """ - Dot-separated path to component (is universally unique). - This attribute is set in :py:func:`~chatsky.pipeline.pipeline.utils.finalize_service_group`. - """ - - self.before_handler = BeforeHandler([] if before_handler is None else before_handler) - self.after_handler = AfterHandler([] if after_handler is None else after_handler) + Validate this component. - if name is not None and (name == "" or "." in name): - raise Exception(f"User defined service name shouldn't be blank or contain '.' (service: {name})!") + :raises ValueError: If component's name is blank or if it contains dots. + :raises Exception: In case component can't be async, but was requested to be. + """ + if self.name is not None: + if self.name == "": + raise ValueError("Name cannot be blank.") + if "." in self.name: + raise ValueError(f"Name cannot contain '.': {self.name!r}.") - if not calculated_async_flag and requested_async_flag: - raise Exception(f"{type(self).__name__} '{name}' can't be asynchronous!") + if not self.calculated_async_flag and self.requested_async_flag: + raise Exception(f"{type(self).__name__} '{self.name}' can't be asynchronous!") + return self def _set_state(self, ctx: Context, value: ComponentExecutionState): """ @@ -160,16 +156,47 @@ async def run_extra_handler(self, stage: ExtraHandlerType, ctx: Context, pipelin logger.warning(f"{type(self).__name__} '{self.name}' {extra_handler.stage} extra handler timed out!") @abc.abstractmethod - async def _run(self, ctx: Context, pipeline: Pipeline) -> None: + async def run_component(self, ctx: Context, pipeline: Pipeline) -> Optional[ComponentExecutionState]: """ - A method for running pipeline component, it is overridden in all its children. - This method is run after the component's timeout is set (if needed). + Run this component. :param ctx: Current dialog :py:class:`~.Context`. :param pipeline: This :py:class:`~.Pipeline`. """ raise NotImplementedError + @property + def computed_name(self) -> str: + """ + Default name that is used if :py:attr:`~.PipelineComponent.name` is not defined. + In case two components in a :py:class:`~.ServiceGroup` have the same + :py:attr:`~.PipelineComponent.computed_name` an incrementing number is appended to the name. + """ + return "noname_service" + + async def _run(self, ctx: Context, pipeline: Pipeline) -> None: + """ + A method for running a pipeline component. Executes extra handlers before and after execution, + launches `run_component` method. This method is run after the component's timeout is set (if needed). + + :param ctx: Current dialog :py:class:`~.Context`. + :param pipeline: This :py:class:`~.Pipeline`. + """ + try: + if await wrap_sync_function_in_async(self.start_condition, ctx, pipeline): + await self.run_extra_handler(ExtraHandlerType.BEFORE, ctx, pipeline) + + self._set_state(ctx, ComponentExecutionState.RUNNING) + if await self.run_component(ctx, pipeline) is not ComponentExecutionState.FAILED: + self._set_state(ctx, ComponentExecutionState.FINISHED) + + await self.run_extra_handler(ExtraHandlerType.AFTER, ctx, pipeline) + else: + self._set_state(ctx, ComponentExecutionState.NOT_RUN) + except Exception as exc: + self._set_state(ctx, ComponentExecutionState.FAILED) + logger.error(f"Service '{self.name}' execution failed!", exc_info=exc) + async def __call__(self, ctx: Context, pipeline: Pipeline) -> Optional[Awaitable]: """ A method for calling pipeline components. diff --git a/chatsky/pipeline/pipeline/pipeline.py b/chatsky/pipeline/pipeline/pipeline.py index 92d62f684..036e0e286 100644 --- a/chatsky/pipeline/pipeline/pipeline.py +++ b/chatsky/pipeline/pipeline/pipeline.py @@ -16,7 +16,9 @@ import asyncio import logging +from functools import cached_property from typing import Union, List, Dict, Optional, Hashable, Callable +from pydantic import BaseModel, Field, model_validator, computed_field from chatsky.context_storages import DBContextStorage from chatsky.script import Script, Context, ActorStage @@ -26,118 +28,145 @@ from chatsky.messengers.console import CLIMessengerInterface from chatsky.messengers.common import MessengerInterface from chatsky.slots.slots import GroupSlot -from ..service.group import ServiceGroup +from chatsky.pipeline.service.group import ServiceGroup +from chatsky.pipeline.service.extra import ComponentExtraHandler from ..types import ( - ServiceBuilder, - ServiceGroupBuilder, - PipelineBuilder, GlobalExtraHandlerType, ExtraHandlerFunction, - ExtraHandlerBuilder, ) -from .utils import finalize_service_group, pretty_format_component_info_dict -from chatsky.pipeline.pipeline.actor import Actor +from .utils import finalize_service_group +from chatsky.pipeline.pipeline.actor import Actor, default_condition_handler logger = logging.getLogger(__name__) -ACTOR = "ACTOR" - -class Pipeline: +class Pipeline(BaseModel, extra="forbid", arbitrary_types_allowed=True): """ Class that automates service execution and creates service pipeline. It accepts constructor parameters: + """ - :param components: (required) A :py:data:`~.ServiceGroupBuilder` object, - that will be transformed to root service group. It should include :py:class:`~.Actor`, - but only once (raises exception otherwise). It will always be named pipeline. - :param script: (required) A :py:class:`~.Script` instance (object or dict). - :param start_label: (required) Actor start label. - :param fallback_label: Actor fallback label. - :param label_priority: Default priority value for all actor :py:const:`labels ` - where there is no priority. Defaults to `1.0`. - :param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`. - :param slots: Slots configuration. - :param handlers: This variable is responsible for the usage of external handlers on - the certain stages of work of :py:class:`~chatsky.script.Actor`. - - - key: :py:class:`~chatsky.script.ActorStage` - Stage in which the handler is called. - - value: List[Callable] - The list of called handlers for each stage. Defaults to an empty `dict`. - - :param messenger_interface: An `AbsMessagingInterface` instance for this pipeline. - :param context_storage: An :py:class:`~.DBContextStorage` instance for this pipeline or - a dict to store dialog :py:class:`~.Context`. - :param before_handler: List of `ExtraHandlerBuilder` to add to the group. - :type before_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param after_handler: List of `ExtraHandlerBuilder` to add to the group. - :type after_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param timeout: Timeout to add to pipeline root service group. - :param optimization_warnings: Asynchronous pipeline optimization check request flag; - warnings will be sent to logs. Additionally it has some calculated fields: - - - `_services_pipeline` is a pipeline root :py:class:`~.ServiceGroup` object, - - `actor` is a pipeline actor, found among services. - :param parallelize_processing: This flag determines whether or not the functions - defined in the ``PRE_RESPONSE_PROCESSING`` and ``PRE_TRANSITIONS_PROCESSING`` sections - of the script should be parallelized over respective groups. - - """ - - def __init__( - self, - components: ServiceGroupBuilder, - script: Union[Script, Dict], - start_label: NodeLabel2Type, - fallback_label: Optional[NodeLabel2Type] = None, - label_priority: float = 1.0, - condition_handler: Optional[Callable] = None, - slots: Optional[Union[GroupSlot, Dict]] = None, - handlers: Optional[Dict[ActorStage, List[Callable]]] = None, - messenger_interface: Optional[MessengerInterface] = None, - context_storage: Optional[Union[DBContextStorage, Dict]] = None, - before_handler: Optional[ExtraHandlerBuilder] = None, - after_handler: Optional[ExtraHandlerBuilder] = None, - timeout: Optional[float] = None, - optimization_warnings: bool = False, - parallelize_processing: bool = False, - ): - self.actor: Actor = None - self.messenger_interface = CLIMessengerInterface() if messenger_interface is None else messenger_interface - self.context_storage = {} if context_storage is None else context_storage - self.slots = GroupSlot.model_validate(slots) if slots is not None else None - self._services_pipeline = ServiceGroup( - components, - before_handler=before_handler, - after_handler=after_handler, - timeout=timeout, + pre_services: ServiceGroup = Field(default_factory=list) + """ + List of :py:class:`~.Service` or :py:class:`~.ServiceGroup` + that will be executed before Actor. + """ + post_services: ServiceGroup = Field(default_factory=list) + """ + List of :py:class:`~.Service` or :py:class:`~.ServiceGroup` that will be + executed after :py:class:`~.Actor`. It constructs root + service group by merging `pre_services` + actor + `post_services`. It will always be named pipeline. + """ + script: Union[Script, Dict] + """ + (required) A :py:class:`~.Script` instance (object or dict). + """ + start_label: NodeLabel2Type + """ + (required) :py:class:`~.Actor` start label. + """ + fallback_label: Optional[NodeLabel2Type] = None + """ + :py:class:`~.Actor` fallback label. + """ + label_priority: float = 1.0 + """ + Default priority value for all actor :py:const:`labels ` + where there is no priority. Defaults to `1.0`. + """ + condition_handler: Callable = Field(default=default_condition_handler) + """ + Handler that processes a call of actor condition functions. Defaults to `None`. + """ + slots: GroupSlot = Field(default_factory=GroupSlot) + """ + Slots configuration. + """ + handlers: Dict[ActorStage, List[Callable]] = Field(default_factory=dict) + """ + This variable is responsible for the usage of external handlers on + the certain stages of work of :py:class:`~chatsky.script.Actor`. + + - key: :py:class:`~chatsky.script.ActorStage` - Stage in which the handler is called. + - value: List[Callable] - The list of called handlers for each stage. Defaults to an empty `dict`. + + """ + messenger_interface: MessengerInterface = Field(default_factory=CLIMessengerInterface) + """ + An `AbsMessagingInterface` instance for this pipeline. + """ + context_storage: Union[DBContextStorage, Dict] = Field(default_factory=dict) + """ + A :py:class:`~.DBContextStorage` instance for this pipeline or + a dict to store dialog :py:class:`~.Context`. + """ + before_handler: ComponentExtraHandler = Field(default_factory=list) + """ + List of :py:class:`~._ComponentExtraHandler` to add to the group. + """ + after_handler: ComponentExtraHandler = Field(default_factory=list) + """ + List of :py:class:`~._ComponentExtraHandler` to add to the group. + """ + timeout: Optional[float] = None + """ + Timeout to add to pipeline root service group. + """ + optimization_warnings: bool = False + """ + Asynchronous pipeline optimization check request flag; + warnings will be sent to logs. Additionally, it has some calculated fields: + + - `_services_pipeline` is a pipeline root :py:class:`~.ServiceGroup` object, + - `actor` is a pipeline actor, found among services. + + """ + parallelize_processing: bool = False + """ + This flag determines whether or not the functions + defined in the ``PRE_RESPONSE_PROCESSING`` and ``PRE_TRANSITIONS_PROCESSING`` sections + of the script should be parallelized over respective groups. + """ + _clean_turn_cache: Optional[bool] + + @computed_field + @cached_property + def actor(self) -> Actor: + return Actor( + script=self.script, + start_label=self.start_label, + fallback_label=self.fallback_label, + label_priority=self.label_priority, + condition_handler=self.condition_handler, + handlers=self.handlers, ) - self._services_pipeline.name = "pipeline" - self._services_pipeline.path = ".pipeline" - actor_exists = finalize_service_group(self._services_pipeline, path=self._services_pipeline.path) - if not actor_exists: - raise Exception("Actor not found in the pipeline!") - else: - self.set_actor( - script, - start_label, - fallback_label, - label_priority, - condition_handler, - handlers, - ) - if self.actor is None: - raise Exception("Actor wasn't initialized correctly!") + @computed_field + @cached_property + def _services_pipeline(self) -> ServiceGroup: + components = [self.pre_services, self.actor, self.post_services] + services_pipeline = ServiceGroup( + components=components, + before_handler=self.before_handler, + after_handler=self.after_handler, + timeout=self.timeout, + ) + services_pipeline.name = "pipeline" + services_pipeline.path = ".pipeline" + return services_pipeline - if optimization_warnings: - self._services_pipeline.log_optimization_warnings() + @model_validator(mode="after") + def __pipeline_init__(self): + finalize_service_group(self._services_pipeline, path=self._services_pipeline.path) - self.parallelize_processing = parallelize_processing + if self.optimization_warnings: + self._services_pipeline.log_optimization_warnings() # NB! The following API is highly experimental and may be removed at ANY time WITHOUT FURTHER NOTICE!! self._clean_turn_cache = True if self._clean_turn_cache: self.actor._clean_turn_cache = False + return self def add_global_handler( self, @@ -193,123 +222,6 @@ def info_dict(self) -> dict: "services": [self._services_pipeline.info_dict], } - def pretty_format(self, show_extra_handlers: bool = False, indent: int = 4) -> str: - """ - Method for receiving pretty-formatted string description of the pipeline. - Resulting string structure is somewhat similar to YAML string. - Should be used in debugging/logging purposes and should not be parsed. - - :param show_wrappers: Whether to include Wrappers or not (could be many and/or generated). - :param indent: Offset from new line to add before component children. - """ - return pretty_format_component_info_dict(self.info_dict, show_extra_handlers, indent=indent) - - @classmethod - def from_script( - cls, - script: Union[Script, Dict], - start_label: NodeLabel2Type, - fallback_label: Optional[NodeLabel2Type] = None, - label_priority: float = 1.0, - condition_handler: Optional[Callable] = None, - slots: Optional[Union[GroupSlot, Dict]] = None, - parallelize_processing: bool = False, - handlers: Optional[Dict[ActorStage, List[Callable]]] = None, - context_storage: Optional[Union[DBContextStorage, Dict]] = None, - messenger_interface: Optional[MessengerInterface] = None, - pre_services: Optional[List[Union[ServiceBuilder, ServiceGroupBuilder]]] = None, - post_services: Optional[List[Union[ServiceBuilder, ServiceGroupBuilder]]] = None, - ) -> "Pipeline": - """ - Pipeline script-based constructor. - It creates :py:class:`~.Actor` object and wraps it with pipeline. - NB! It is generally not designed for projects with complex structure. - :py:class:`~.Service` and :py:class:`~.ServiceGroup` customization - becomes not as obvious as it could be with it. - Should be preferred for simple workflows with Actor auto-execution. - - :param script: (required) A :py:class:`~.Script` instance (object or dict). - :param start_label: (required) Actor start label. - :param fallback_label: Actor fallback label. - :param label_priority: Default priority value for all actor :py:const:`labels ` - where there is no priority. Defaults to `1.0`. - :param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`. - :param slots: Slots configuration. - :param parallelize_processing: This flag determines whether or not the functions - defined in the ``PRE_RESPONSE_PROCESSING`` and ``PRE_TRANSITIONS_PROCESSING`` sections - of the script should be parallelized over respective groups. - :param handlers: This variable is responsible for the usage of external handlers on - the certain stages of work of :py:class:`~chatsky.script.Actor`. - - - key: :py:class:`~chatsky.script.ActorStage` - Stage in which the handler is called. - - value: List[Callable] - The list of called handlers for each stage. Defaults to an empty `dict`. - - :param context_storage: An :py:class:`~.DBContextStorage` instance for this pipeline - or a dict to store dialog :py:class:`~.Context`. - :param messenger_interface: An instance for this pipeline. - :param pre_services: List of :py:data:`~.ServiceBuilder` or - :py:data:`~.ServiceGroupBuilder` that will be executed before Actor. - :type pre_services: Optional[List[Union[ServiceBuilder, ServiceGroupBuilder]]] - :param post_services: List of :py:data:`~.ServiceBuilder` or - :py:data:`~.ServiceGroupBuilder` that will be executed after Actor. - It constructs root service group by merging `pre_services` + actor + `post_services`. - :type post_services: Optional[List[Union[ServiceBuilder, ServiceGroupBuilder]]] - """ - pre_services = [] if pre_services is None else pre_services - post_services = [] if post_services is None else post_services - return cls( - script=script, - start_label=start_label, - fallback_label=fallback_label, - label_priority=label_priority, - condition_handler=condition_handler, - slots=slots, - parallelize_processing=parallelize_processing, - handlers=handlers, - messenger_interface=messenger_interface, - context_storage=context_storage, - components=[*pre_services, ACTOR, *post_services], - ) - - def set_actor( - self, - script: Union[Script, Dict], - start_label: NodeLabel2Type, - fallback_label: Optional[NodeLabel2Type] = None, - label_priority: float = 1.0, - condition_handler: Optional[Callable] = None, - handlers: Optional[Dict[ActorStage, List[Callable]]] = None, - ): - """ - Set actor for the current pipeline and conducts necessary checks. - Reset actor to previous if any errors are found. - - :param script: (required) A :py:class:`~.Script` instance (object or dict). - :param start_label: (required) Actor start label. - The start node of :py:class:`~chatsky.script.Script`. The execution begins with it. - :param fallback_label: Actor fallback label. The label of :py:class:`~chatsky.script.Script`. - Dialog comes into that label if all other transitions failed, - or there was an error while executing the scenario. - :param label_priority: Default priority value for all actor :py:const:`labels ` - where there is no priority. Defaults to `1.0`. - :param condition_handler: Handler that processes a call of actor condition functions. Defaults to `None`. - :param handlers: This variable is responsible for the usage of external handlers on - the certain stages of work of :py:class:`~chatsky.script.Actor`. - - - key :py:class:`~chatsky.script.ActorStage` - Stage in which the handler is called. - - value List[Callable] - The list of called handlers for each stage. Defaults to an empty `dict`. - """ - self.actor = Actor(script, start_label, fallback_label, label_priority, condition_handler, handlers) - - @classmethod - def from_dict(cls, dictionary: PipelineBuilder) -> "Pipeline": - """ - Pipeline dictionary-based constructor. - Dictionary should have the fields defined in Pipeline main constructor, - it will be split and passed to it as `**kwargs`. - """ - return cls(**dictionary) - async def _run_pipeline( self, request: Message, ctx_id: Optional[Hashable] = None, update_ctx_misc: Optional[dict] = None ) -> Context: diff --git a/chatsky/pipeline/pipeline/utils.py b/chatsky/pipeline/pipeline/utils.py index 752bde18c..931bade56 100644 --- a/chatsky/pipeline/pipeline/utils.py +++ b/chatsky/pipeline/pipeline/utils.py @@ -6,107 +6,47 @@ """ import collections -from typing import Union, List -from inspect import isfunction +from typing import List -from ..service.service import Service +from .component import PipelineComponent from ..service.group import ServiceGroup -def pretty_format_component_info_dict( - service: dict, - show_extra_handlers: bool, - offset: str = "", - extra_handlers_key: str = "extra_handlers", - type_key: str = "type", - name_key: str = "name", - indent: int = 4, -) -> str: - """ - Function for dumping any pipeline components info dictionary (received from `info_dict` property) as a string. - Resulting string is formatted with YAML-like format, however it's not strict and shouldn't be parsed. - However, most preferable usage is via `pipeline.pretty_format`. - - :param service: (required) Pipeline components info dictionary. - :param show_extra_handlers: (required) Whether to include Extra Handlers or not (could be many and/or generated). - :param offset: Current level new line offset. - :param extra_handlers_key: Key that is mapped to Extra Handlers lists. - :param type_key: Key that is mapped to components type name. - :param name_key: Key that is mapped to components name. - :param indent: Current level new line offset (whitespace number). - :return: Formatted string - """ - indent = " " * indent - representation = f"{offset}{service.get(type_key, '[None]')}%s:\n" % ( - f" '{service.get(name_key, '[None]')}'" if name_key in service else "" - ) - for key, value in service.items(): - if key not in (type_key, name_key, extra_handlers_key) or (key == extra_handlers_key and show_extra_handlers): - if isinstance(value, List): - if len(value) > 0: - values = [ - pretty_format_component_info_dict(instance, show_extra_handlers, f"{indent * 2}{offset}") - for instance in value - ] - value_str = "\n%s" % "\n".join(values) - else: - value_str = "[None]" - else: - value_str = str(value) - representation += f"{offset}{indent}{key}: {value_str}\n" - return representation[:-1] - - -def rename_component_incrementing( - service: Union[Service, ServiceGroup], collisions: List[Union[Service, ServiceGroup]] -) -> str: +def rename_component_incrementing(component: PipelineComponent, collisions: List[PipelineComponent]) -> str: """ Function for generating new name for a pipeline component, that has similar name with other components in the same group. The name is generated according to these rules: - - If service's handler is "ACTOR", it is named `actor`. - - If service's handler is `Callable`, it is named after this `callable`. + - If component is an `Actor`, it is named `actor`. + - If component is a `Service` and the service's handler is `Callable`, it is named after this `callable`. - If it's a service group, it is named `service_group`. - - Otherwise, it is names `noname_service`. + - Otherwise, it is named `noname_service`. - | After that, `_[NUMBER]` is added to the resulting name, where `_[NUMBER]` is number of components with the same name in current service group. - :param service: Service to be renamed. - :param collisions: Services in the same service group as service. + :param component: Component to be renamed. + :param collisions: Components in the same service group as component. :return: Generated name """ - if isinstance(service, Service) and isinstance(service.handler, str) and service.handler == "ACTOR": - base_name = "actor" - elif isinstance(service, Service) and callable(service.handler): - if isfunction(service.handler): - base_name = service.handler.__name__ - else: - base_name = service.handler.__class__.__name__ - elif isinstance(service, ServiceGroup): - base_name = "service_group" - else: - base_name = "noname_service" - + base_name = component.computed_name name_index = 0 while f"{base_name}_{name_index}" in [component.name for component in collisions]: name_index += 1 return f"{base_name}_{name_index}" -def finalize_service_group(service_group: ServiceGroup, path: str = ".") -> bool: +def finalize_service_group(service_group: ServiceGroup, path: str = ".") -> None: """ Function that iterates through a service group (and all its subgroups), finalizing component's names and paths in it. Components are renamed only if user didn't set a name for them. Their paths are also generated here. - It also searches for "ACTOR" in the group, throwing exception if no actor or multiple actors found. :param service_group: Service group to resolve name collisions in. :param path: A prefix for component paths -- path of `component` is equal to `{path}.{component.name}`. Defaults to ".". """ - actor = False names_counter = collections.Counter([component.name for component in service_group.components]) for component in service_group.components: if component.name is None: @@ -115,16 +55,5 @@ def finalize_service_group(service_group: ServiceGroup, path: str = ".") -> bool raise Exception(f"User defined service name collision ({path})!") component.path = f"{path}.{component.name}" - if isinstance(component, Service) and isinstance(component.handler, str) and component.handler == "ACTOR": - actor_found = True - elif isinstance(component, ServiceGroup): - actor_found = finalize_service_group(component, f"{path}.{component.name}") - else: - actor_found = False - - if actor_found: - if not actor: - actor = actor_found - else: - raise Exception(f"More than one actor found in group ({path})!") - return actor + if isinstance(component, ServiceGroup): + finalize_service_group(component, f"{path}.{component.name}") diff --git a/chatsky/pipeline/service/extra.py b/chatsky/pipeline/service/extra.py index 8a8a65a9b..f7475ee58 100644 --- a/chatsky/pipeline/service/extra.py +++ b/chatsky/pipeline/service/extra.py @@ -10,16 +10,15 @@ import asyncio import logging import inspect -from typing import Optional, List, TYPE_CHECKING +from typing import Optional, List, TYPE_CHECKING, Any, ClassVar +from pydantic import BaseModel, computed_field, model_validator, Field from chatsky.script import Context -from .utils import collect_defined_constructor_parameters_to_dict, _get_attrs_with_updates from chatsky.utils.devel.async_helpers import wrap_sync_function_in_async from ..types import ( ServiceRuntimeInfo, ExtraHandlerType, - ExtraHandlerBuilder, ExtraHandlerFunction, ExtraHandlerRuntimeInfo, ) @@ -30,53 +29,55 @@ from chatsky.pipeline.pipeline.pipeline import Pipeline -class _ComponentExtraHandler: +class ComponentExtraHandler(BaseModel, extra="forbid", arbitrary_types_allowed=True): """ Class, representing an extra pipeline component handler. A component extra handler is a set of functions, attached to pipeline component (before or after it). Extra handlers should execute supportive tasks (like time or resources measurement, minor data transformations). Extra handlers should NOT edit context or pipeline, use services for that purpose instead. + """ - :param functions: An `ExtraHandlerBuilder` object, an `_ComponentExtraHandler` instance, - a dict or a list of :py:data:`~.ExtraHandlerFunction`. - :type functions: :py:data:`~.ExtraHandlerBuilder` - :param stage: An :py:class:`~.ExtraHandlerType`, specifying whether this handler will be executed before or - after pipeline component. - :param timeout: (for asynchronous only!) Maximum component execution time (in seconds), - if it exceeds this time, it is interrupted. - :param asynchronous: Requested asynchronous property. + functions: List[ExtraHandlerFunction] = Field(default_factory=list) + """ + A list or instance of :py:data:`~.ExtraHandlerFunction`. + """ + stage: ClassVar[ExtraHandlerType] = ExtraHandlerType.UNDEFINED + """ + An :py:class:`~.ExtraHandlerType`, specifying whether this handler will + be executed before or after pipeline component. + """ + timeout: Optional[float] = None + """ + (for asynchronous only!) Maximum component execution time (in seconds), + if it exceeds this time, it is interrupted. + """ + requested_async_flag: Optional[bool] = None + """ + Requested asynchronous property. """ - def __init__( - self, - functions: ExtraHandlerBuilder, - stage: ExtraHandlerType = ExtraHandlerType.UNDEFINED, - timeout: Optional[float] = None, - asynchronous: Optional[bool] = None, - ): - overridden_parameters = collect_defined_constructor_parameters_to_dict( - timeout=timeout, asynchronous=asynchronous - ) - if isinstance(functions, _ComponentExtraHandler): - self.__init__( - **_get_attrs_with_updates( - functions, - ("calculated_async_flag", "stage"), - {"requested_async_flag": "asynchronous"}, - overridden_parameters, - ) - ) - elif isinstance(functions, dict): - functions.update(overridden_parameters) - self.__init__(**functions) - elif isinstance(functions, List): - self.functions = functions - self.timeout = timeout - self.requested_async_flag = asynchronous - self.calculated_async_flag = all([asyncio.iscoroutinefunction(func) for func in self.functions]) - self.stage = stage + @model_validator(mode="before") + @classmethod + def functions_validator(cls, data: Any): + """ + Add support for initializing from a `Callable` or List[`Callable`]. + Casts `functions` to `list` if it's not already. + """ + if isinstance(data, list): + result = {"functions": data} + elif callable(data): + result = {"functions": [data]} else: - raise Exception(f"Unknown type for {type(self).__name__} {functions}") + result = data + + if isinstance(result, dict): + if ("functions" in result) and (not isinstance(result["functions"], list)): + result["functions"] = [result["functions"]] + return result + + @computed_field(repr=False) + def calculated_async_flag(self) -> bool: + return all([asyncio.iscoroutinefunction(func) for func in self.functions]) @property def asynchronous(self) -> bool: @@ -168,47 +169,35 @@ def info_dict(self) -> dict: } -class BeforeHandler(_ComponentExtraHandler): +class BeforeHandler(ComponentExtraHandler): """ A handler for extra functions that are executed before the component's main function. - :param functions: A callable or a list of callables that will be executed + :param functions: A list of callables that will be executed before the component's main function. - :type functions: ExtraHandlerBuilder + :type functions: List[ExtraHandlerFunction] :param timeout: Optional timeout for the execution of the extra functions, in seconds. - :param asynchronous: Optional flag that indicates whether the extra functions + :param requested_async_flag: Optional flag that indicates whether the extra functions should be executed asynchronously. The default value of the flag is True if all the functions in this handler are asynchronous. """ - def __init__( - self, - functions: ExtraHandlerBuilder, - timeout: Optional[int] = None, - asynchronous: Optional[bool] = None, - ): - super().__init__(functions, ExtraHandlerType.BEFORE, timeout, asynchronous) + stage: ClassVar[ExtraHandlerType] = ExtraHandlerType.BEFORE -class AfterHandler(_ComponentExtraHandler): +class AfterHandler(ComponentExtraHandler): """ A handler for extra functions that are executed after the component's main function. - :param functions: A callable or a list of callables that will be executed + :param functions: A list of callables that will be executed after the component's main function. - :type functions: ExtraHandlerBuilder + :type functions: List[ExtraHandlerFunction] :param timeout: Optional timeout for the execution of the extra functions, in seconds. - :param asynchronous: Optional flag that indicates whether the extra functions + :param requested_async_flag: Optional flag that indicates whether the extra functions should be executed asynchronously. The default value of the flag is True if all the functions in this handler are asynchronous. """ - def __init__( - self, - functions: ExtraHandlerBuilder, - timeout: Optional[int] = None, - asynchronous: Optional[bool] = None, - ): - super().__init__(functions, ExtraHandlerType.AFTER, timeout, asynchronous) + stage: ClassVar[ExtraHandlerType] = ExtraHandlerType.AFTER diff --git a/chatsky/pipeline/service/group.py b/chatsky/pipeline/service/group.py index 22b8bae0d..8c2efc37a 100644 --- a/chatsky/pipeline/service/group.py +++ b/chatsky/pipeline/service/group.py @@ -11,21 +11,21 @@ from __future__ import annotations import asyncio import logging -from typing import Optional, List, Union, Awaitable, TYPE_CHECKING +from typing import List, Union, Awaitable, TYPE_CHECKING, Any, Optional + +from chatsky.pipeline import BeforeHandler, AfterHandler, always_start_condition +from pydantic import model_validator, Field from chatsky.script import Context +from ..pipeline.actor import Actor -from .utils import collect_defined_constructor_parameters_to_dict, _get_attrs_with_updates from ..pipeline.component import PipelineComponent from ..types import ( - StartConditionCheckerFunction, ComponentExecutionState, - ServiceGroupBuilder, GlobalExtraHandlerType, ExtraHandlerConditionFunction, ExtraHandlerFunction, - ExtraHandlerBuilder, - ExtraHandlerType, + StartConditionCheckerFunction, ) from .service import Service @@ -43,75 +43,63 @@ class ServiceGroup(PipelineComponent): Components in synchronous groups are executed consequently (no matter is they are synchronous or asynchronous). Components in asynchronous groups are executed simultaneously. Group can be asynchronous only if all components in it are asynchronous. - - :param components: A `ServiceGroupBuilder` object, that will be added to the group. - :type components: :py:data:`~.ServiceGroupBuilder` - :param before_handler: List of `ExtraHandlerBuilder` to add to the group. - :type before_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param after_handler: List of `ExtraHandlerBuilder` to add to the group. - :type after_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param timeout: Timeout to add to the group. - :param asynchronous: Requested asynchronous property. - :param start_condition: :py:data:`~.StartConditionCheckerFunction` that is invoked before each group execution; - group is executed only if it returns `True`. - :param name: Requested group name. """ - def __init__( - self, - components: ServiceGroupBuilder, - before_handler: Optional[ExtraHandlerBuilder] = None, - after_handler: Optional[ExtraHandlerBuilder] = None, - timeout: Optional[float] = None, - asynchronous: Optional[bool] = None, - start_condition: Optional[StartConditionCheckerFunction] = None, - name: Optional[str] = None, - ): - overridden_parameters = collect_defined_constructor_parameters_to_dict( - before_handler=before_handler, - after_handler=after_handler, - timeout=timeout, - asynchronous=asynchronous, - start_condition=start_condition, - name=name, - ) - if isinstance(components, ServiceGroup): - self.__init__( - **_get_attrs_with_updates( - components, - ( - "calculated_async_flag", - "path", - ), - {"requested_async_flag": "asynchronous"}, - overridden_parameters, - ) - ) - elif isinstance(components, dict): - components.update(overridden_parameters) - self.__init__(**components) - elif isinstance(components, List): - self.components = self._create_components(components) - calc_async = all([service.asynchronous for service in self.components]) - super(ServiceGroup, self).__init__( - before_handler, after_handler, timeout, asynchronous, calc_async, start_condition, name - ) + components: List[ + Union[ + Actor, + Service, + ServiceGroup, + ] + ] + """ + A :py:class:`~.ServiceGroup` object, that will be added to the group. + """ + # Inherited fields repeated. Don't delete these, they're needed for documentation! + before_handler: BeforeHandler = Field(default_factory=BeforeHandler) + after_handler: AfterHandler = Field(default_factory=AfterHandler) + timeout: Optional[float] = None + requested_async_flag: Optional[bool] = None + start_condition: StartConditionCheckerFunction = Field(default=always_start_condition) + name: Optional[str] = None + path: Optional[str] = None + + @model_validator(mode="before") + @classmethod + def components_validator(cls, data: Any): + """ + Add support for initializing from a `Callable`, `List` + and :py:class:`~.PipelineComponent` (such as :py:class:`~.Service`) + Casts `components` to `list` if it's not already. + """ + if isinstance(data, list): + result = {"components": data} + elif callable(data) or isinstance(data, PipelineComponent): + result = {"components": [data]} else: - raise Exception(f"Unknown type for ServiceGroup {components}") + result = data + + if isinstance(result, dict): + if ("components" in result) and (not isinstance(result["components"], list)): + result["components"] = [result["components"]] + return result + + @model_validator(mode="after") + def __calculate_async_flag__(self): + self.calculated_async_flag = all([service.asynchronous for service in self.components]) + return self - async def _run_services_group(self, ctx: Context, pipeline: Pipeline) -> None: + async def run_component(self, ctx: Context, pipeline: Pipeline) -> Optional[ComponentExecutionState]: """ - Method for running this service group. + Method for running this service group. Catches runtime exceptions and logs them. It doesn't include extra handlers execution, start condition checking or error handling - pure execution only. - Executes components inside the group based on its `asynchronous` property. + Executes components inside the group based on its :py:attr:`~.PipelineComponent.asynchronous` property. Collects information about their execution state - group is finished successfully only if all components in it finished successfully. :param ctx: Current dialog context. :param pipeline: The current pipeline. """ - self._set_state(ctx, ComponentExecutionState.RUNNING) - if self.asynchronous: service_futures = [service(ctx, pipeline) for service in self.components] for service, future in zip(self.components, await asyncio.gather(*service_futures, return_exceptions=True)): @@ -128,33 +116,8 @@ async def _run_services_group(self, ctx: Context, pipeline: Pipeline) -> None: await service_result failed = any([service.get_state(ctx) == ComponentExecutionState.FAILED for service in self.components]) - self._set_state(ctx, ComponentExecutionState.FAILED if failed else ComponentExecutionState.FINISHED) - - async def _run( - self, - ctx: Context, - pipeline: Pipeline, - ) -> None: - """ - Method for handling this group execution. - Executes extra handlers before and after execution, checks start condition and catches runtime exceptions. - - :param ctx: Current dialog context. - :param pipeline: The current pipeline. - """ - await self.run_extra_handler(ExtraHandlerType.BEFORE, ctx, pipeline) - - try: - if self.start_condition(ctx, pipeline): - await self._run_services_group(ctx, pipeline) - else: - self._set_state(ctx, ComponentExecutionState.NOT_RUN) - - except Exception as exc: - self._set_state(ctx, ComponentExecutionState.FAILED) - logger.error(f"ServiceGroup '{self.name}' execution failed!", exc_info=exc) - - await self.run_extra_handler(ExtraHandlerType.AFTER, ctx, pipeline) + if failed: + return ComponentExecutionState.FAILED def log_optimization_warnings(self): """ @@ -171,7 +134,7 @@ def log_optimization_warnings(self): :return: `None` """ for service in self.components: - if isinstance(service, Service): + if not isinstance(service, ServiceGroup): if ( service.calculated_async_flag and service.requested_async_flag is not None @@ -195,7 +158,7 @@ def add_extra_handler( self, global_extra_handler_type: GlobalExtraHandlerType, extra_handler: ExtraHandlerFunction, - condition: ExtraHandlerConditionFunction = lambda _: True, + condition: ExtraHandlerConditionFunction = lambda _: False, ): """ Method for adding a global extra handler to this group. @@ -213,10 +176,14 @@ def add_extra_handler( for service in self.components: if not condition(service.path): continue - if isinstance(service, Service): - service.add_extra_handler(global_extra_handler_type, extra_handler) - else: + if isinstance(service, ServiceGroup): service.add_extra_handler(global_extra_handler_type, extra_handler, condition) + else: + service.add_extra_handler(global_extra_handler_type, extra_handler) + + @property + def computed_name(self) -> str: + return "service_group" @property def info_dict(self) -> dict: @@ -227,22 +194,3 @@ def info_dict(self) -> dict: representation = super(ServiceGroup, self).info_dict representation.update({"services": [service.info_dict for service in self.components]}) return representation - - @staticmethod - def _create_components(services: ServiceGroupBuilder) -> List[Union[Service, "ServiceGroup"]]: - """ - Utility method, used to create inner components, judging by their nature. - Services are created from services and dictionaries. - ServiceGroups are created from service groups and lists. - - :param services: ServiceGroupBuilder object (a `ServiceGroup` instance or a list). - :type services: :py:data:`~.ServiceGroupBuilder` - :return: List of services and service groups. - """ - handled_services: List[Union[Service, "ServiceGroup"]] = [] - for service in services: - if isinstance(service, List) or isinstance(service, ServiceGroup): - handled_services.append(ServiceGroup(service)) - else: - handled_services.append(Service(service)) - return handled_services diff --git a/chatsky/pipeline/service/service.py b/chatsky/pipeline/service/service.py index fdf43f0bb..1796ae6b3 100644 --- a/chatsky/pipeline/service/service.py +++ b/chatsky/pipeline/service/service.py @@ -13,20 +13,20 @@ from __future__ import annotations import logging import inspect -from typing import Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Optional, Callable +from pydantic import model_validator, Field from chatsky.script import Context -from .utils import collect_defined_constructor_parameters_to_dict, _get_attrs_with_updates + from chatsky.utils.devel.async_helpers import wrap_sync_function_in_async +from chatsky.pipeline import always_start_condition from ..types import ( - ServiceBuilder, + ServiceFunction, StartConditionCheckerFunction, - ComponentExecutionState, - ExtraHandlerBuilder, - ExtraHandlerType, ) from ..pipeline.component import PipelineComponent +from .extra import BeforeHandler, AfterHandler logger = logging.getLogger(__name__) @@ -40,73 +40,40 @@ class Service(PipelineComponent): Service can be included into pipeline as object or a dictionary. Service group can be synchronous or asynchronous. Service can be asynchronous only if its handler is a coroutine. + """ - :param handler: A service function or an actor. - :type handler: :py:data:`~.ServiceBuilder` - :param before_handler: List of `ExtraHandlerBuilder` to add to the group. - :type before_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param after_handler: List of `ExtraHandlerBuilder` to add to the group. - :type after_handler: Optional[:py:data:`~.ExtraHandlerBuilder`] - :param timeout: Timeout to add to the group. - :param asynchronous: Requested asynchronous property. - :param start_condition: StartConditionCheckerFunction that is invoked before each service execution; - service is executed only if it returns `True`. - :type start_condition: Optional[:py:data:`~.StartConditionCheckerFunction`] - :param name: Requested service name. + handler: ServiceFunction """ + A :py:data:`~.ServiceFunction`. + """ + # Inherited fields repeated. Don't delete these, they're needed for documentation! + before_handler: BeforeHandler = Field(default_factory=BeforeHandler) + after_handler: AfterHandler = Field(default_factory=AfterHandler) + timeout: Optional[float] = None + requested_async_flag: Optional[bool] = None + start_condition: StartConditionCheckerFunction = Field(default=always_start_condition) + name: Optional[str] = None + path: Optional[str] = None + + @model_validator(mode="before") + @classmethod + def handler_validator(cls, data: Any): + """ + Add support for initializing from a `Callable`. + """ + if isinstance(data, Callable): + return {"handler": data} + return data - def __init__( - self, - handler: ServiceBuilder, - before_handler: Optional[ExtraHandlerBuilder] = None, - after_handler: Optional[ExtraHandlerBuilder] = None, - timeout: Optional[float] = None, - asynchronous: Optional[bool] = None, - start_condition: Optional[StartConditionCheckerFunction] = None, - name: Optional[str] = None, - ): - overridden_parameters = collect_defined_constructor_parameters_to_dict( - before_handler=before_handler, - after_handler=after_handler, - timeout=timeout, - asynchronous=asynchronous, - start_condition=start_condition, - name=name, - ) - if isinstance(handler, dict): - handler.update(overridden_parameters) - self.__init__(**handler) - elif isinstance(handler, Service): - self.__init__( - **_get_attrs_with_updates( - handler, - ( - "calculated_async_flag", - "path", - ), - {"requested_async_flag": "asynchronous"}, - overridden_parameters, - ) - ) - elif callable(handler) or isinstance(handler, str) and handler == "ACTOR": - self.handler = handler - super(Service, self).__init__( - before_handler, - after_handler, - timeout, - True, - True, - start_condition, - name, - ) - else: - raise Exception(f"Unknown type of service handler: {handler}") + @model_validator(mode="after") + def __tick_async_flag__(self): + self.calculated_async_flag = True + return self - async def _run_handler(self, ctx: Context, pipeline: Pipeline) -> None: + async def run_component(self, ctx: Context, pipeline: Pipeline) -> None: """ - Method for service `handler` execution. - Handler has three possible signatures, so this method picks the right one to invoke. - These possible signatures are: + Method for running this service. Service 'handler' has three possible signatures, + so this method picks the right one to invoke. These possible signatures are: - (ctx: Context) - accepts current dialog context only. - (ctx: Context, pipeline: Pipeline) - accepts context and current pipeline. @@ -127,55 +94,12 @@ async def _run_handler(self, ctx: Context, pipeline: Pipeline) -> None: else: raise Exception(f"Too many parameters required for service '{self.name}' handler: {handler_params}!") - async def _run_as_actor(self, ctx: Context, pipeline: Pipeline) -> None: - """ - Method for running this service if its handler is an `Actor`. - Catches runtime exceptions. - - :param ctx: Current dialog context. - """ - try: - await pipeline.actor(pipeline, ctx) - self._set_state(ctx, ComponentExecutionState.FINISHED) - except Exception as exc: - self._set_state(ctx, ComponentExecutionState.FAILED) - logger.error(f"Actor '{self.name}' execution failed!", exc_info=exc) - - async def _run_as_service(self, ctx: Context, pipeline: Pipeline) -> None: - """ - Method for running this service if its handler is not an Actor. - Checks start condition and catches runtime exceptions. - - :param ctx: Current dialog context. - :param pipeline: Current pipeline. - """ - try: - if self.start_condition(ctx, pipeline): - self._set_state(ctx, ComponentExecutionState.RUNNING) - await self._run_handler(ctx, pipeline) - self._set_state(ctx, ComponentExecutionState.FINISHED) - else: - self._set_state(ctx, ComponentExecutionState.NOT_RUN) - except Exception as exc: - self._set_state(ctx, ComponentExecutionState.FAILED) - logger.error(f"Service '{self.name}' execution failed!", exc_info=exc) - - async def _run(self, ctx: Context, pipeline: Pipeline) -> None: - """ - Method for handling this service execution. - Executes extra handlers before and after execution, launches `_run_as_actor` or `_run_as_service` method. - - :param ctx: (required) Current dialog context. - :param pipeline: the current pipeline. - """ - await self.run_extra_handler(ExtraHandlerType.BEFORE, ctx, pipeline) - - if isinstance(self.handler, str) and self.handler == "ACTOR": - await self._run_as_actor(ctx, pipeline) + @property + def computed_name(self) -> str: + if inspect.isfunction(self.handler): + return self.handler.__name__ else: - await self._run_as_service(ctx, pipeline) - - await self.run_extra_handler(ExtraHandlerType.AFTER, ctx, pipeline) + return self.handler.__class__.__name__ @property def info_dict(self) -> dict: @@ -184,9 +108,8 @@ def info_dict(self) -> dict: Adds `handler` key to base info dictionary. """ representation = super(Service, self).info_dict - if isinstance(self.handler, str) and self.handler == "ACTOR": - service_representation = "Instance of Actor" - elif callable(self.handler): + # Need to carefully remove this + if callable(self.handler): service_representation = f"Callable '{self.handler.__name__}'" else: service_representation = "[Unknown]" @@ -195,11 +118,11 @@ def info_dict(self) -> dict: def to_service( - before_handler: Optional[ExtraHandlerBuilder] = None, - after_handler: Optional[ExtraHandlerBuilder] = None, + before_handler: BeforeHandler = None, + after_handler: AfterHandler = None, timeout: Optional[int] = None, asynchronous: Optional[bool] = None, - start_condition: Optional[StartConditionCheckerFunction] = None, + start_condition: StartConditionCheckerFunction = always_start_condition, name: Optional[str] = None, ): """ @@ -207,14 +130,16 @@ def to_service( Returns a Service, constructed from this function (taken as a handler). All arguments are passed directly to `Service` constructor. """ + before_handler = BeforeHandler() if before_handler is None else before_handler + after_handler = AfterHandler() if after_handler is None else after_handler - def inner(handler: ServiceBuilder) -> Service: + def inner(handler: ServiceFunction) -> Service: return Service( handler=handler, before_handler=before_handler, after_handler=after_handler, timeout=timeout, - asynchronous=asynchronous, + requested_async_flag=asynchronous, start_condition=start_condition, name=name, ) diff --git a/chatsky/pipeline/service/utils.py b/chatsky/pipeline/service/utils.py deleted file mode 100644 index 651f89b92..000000000 --- a/chatsky/pipeline/service/utils.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -Utility Functions ------------------ -The Utility Functions module contains several utility functions that are commonly used throughout Chatsky. -These functions provide a variety of utility functionality. -""" - -from typing import Any, Optional, Tuple, Mapping - - -def _get_attrs_with_updates( - obj: object, - drop_attrs: Optional[Tuple[str, ...]] = None, - replace_attrs: Optional[Mapping[str, str]] = None, - add_attrs: Optional[Mapping[str, Any]] = None, -) -> dict: - """ - Advanced customizable version of built-in `__dict__` property. - Sometimes during Pipeline construction `Services` (or `ServiceGroups`) should be rebuilt, - e.g. in case of some fields overriding. - This method can be customized to return a dict, - that can be spread (** operator) and passed to Service or ServiceGroup constructor. - Base dict is formed via `vars` built-in function. All "private" or "dunder" fields are omitted. - - :param drop_attrs: A tuple of key names that should be removed from the resulting dict. - :param replace_attrs: A mapping that should be replaced in the resulting dict. - :param add_attrs: A mapping that should be added to the resulting dict. - :return: Resulting dict. - """ - drop_attrs = () if drop_attrs is None else drop_attrs - replace_attrs = {} if replace_attrs is None else dict(replace_attrs) - add_attrs = {} if add_attrs is None else dict(add_attrs) - result = {} - for attribute in vars(obj): - if not attribute.startswith("__") and attribute not in drop_attrs: - if attribute in replace_attrs: - result[replace_attrs[attribute]] = getattr(obj, attribute) - else: - result[attribute] = getattr(obj, attribute) - result.update(add_attrs) - return result - - -def collect_defined_constructor_parameters_to_dict(**kwargs: Any): - """ - Function, that creates dict from non-`None` constructor parameters of pipeline component. - It is used in overriding component parameters, - when service handler or service group service is instance of Service or ServiceGroup (or dict). - It accepts same named parameters as component constructor. - - :return: Dict, containing key-value pairs of these parameters, that are not `None`. - """ - return dict([(key, value) for key, value in kwargs.items() if value is not None]) diff --git a/chatsky/pipeline/types.py b/chatsky/pipeline/types.py index 118532559..56e955855 100644 --- a/chatsky/pipeline/types.py +++ b/chatsky/pipeline/types.py @@ -8,19 +8,14 @@ from __future__ import annotations from enum import unique, Enum -from typing import Callable, Union, Awaitable, Dict, List, Optional, Iterable, Any, Protocol, Hashable, TYPE_CHECKING -from typing_extensions import NotRequired, TypedDict, TypeAlias +from typing import Callable, Union, Awaitable, Dict, Optional, Iterable, Any, Protocol, Hashable, TYPE_CHECKING +from typing_extensions import TypeAlias from pydantic import BaseModel if TYPE_CHECKING: from chatsky.pipeline.pipeline.pipeline import Pipeline - from chatsky.pipeline.service.service import Service - from chatsky.pipeline.service.group import ServiceGroup - from chatsky.pipeline.service.extra import _ComponentExtraHandler - from chatsky.messengers.common.interface import MessengerInterface - from chatsky.context_storages import DBContextStorage - from chatsky.script import Context, ActorStage, NodeLabel2Type, Script, Message + from chatsky.script import Context, Message class PipelineRunnerFunction(Protocol): @@ -178,89 +173,3 @@ class ExtraHandlerRuntimeInfo(BaseModel): Can accept current dialog context, pipeline, and current service info. Can be both synchronous and asynchronous. """ - - -ExtraHandlerBuilder: TypeAlias = Union[ - "_ComponentExtraHandler", - TypedDict( - "WrapperDict", - { - "timeout": NotRequired[Optional[float]], - "asynchronous": NotRequired[bool], - "functions": List[ExtraHandlerFunction], - }, - ), - List[ExtraHandlerFunction], -] -""" -A type, representing anything that can be transformed to ExtraHandlers. -It can be: - -- ExtraHandlerFunction object -- Dictionary, containing keys `timeout`, `asynchronous`, `functions` -""" - - -ServiceBuilder: TypeAlias = Union[ - ServiceFunction, - "Service", - str, - TypedDict( - "ServiceDict", - { - "handler": "ServiceBuilder", - "before_handler": NotRequired[Optional[ExtraHandlerBuilder]], - "after_handler": NotRequired[Optional[ExtraHandlerBuilder]], - "timeout": NotRequired[Optional[float]], - "asynchronous": NotRequired[bool], - "start_condition": NotRequired[StartConditionCheckerFunction], - "name": Optional[str], - }, - ), -] -""" -A type, representing anything that can be transformed to service. -It can be: - -- ServiceFunction (will become handler) -- Service object (will be spread and recreated) -- String 'ACTOR' - the pipeline Actor will be placed there -- Dictionary, containing keys that are present in Service constructor parameters -""" - - -ServiceGroupBuilder: TypeAlias = Union[ - List[Union[ServiceBuilder, List[ServiceBuilder], "ServiceGroup"]], - "ServiceGroup", -] -""" -A type, representing anything that can be transformed to service group. -It can be: - -- List of `ServiceBuilders`, `ServiceGroup` objects and lists (recursive) -- `ServiceGroup` object (will be spread and recreated) -""" - - -PipelineBuilder: TypeAlias = TypedDict( - "PipelineBuilder", - { - "messenger_interface": NotRequired[Optional["MessengerInterface"]], - "context_storage": NotRequired[Optional[Union["DBContextStorage", Dict]]], - "components": ServiceGroupBuilder, - "before_handler": NotRequired[Optional[ExtraHandlerBuilder]], - "after_handler": NotRequired[Optional[ExtraHandlerBuilder]], - "optimization_warnings": NotRequired[bool], - "parallelize_processing": NotRequired[bool], - "script": Union["Script", Dict], - "start_label": "NodeLabel2Type", - "fallback_label": NotRequired[Optional["NodeLabel2Type"]], - "label_priority": NotRequired[float], - "condition_handler": NotRequired[Optional[Callable]], - "handlers": NotRequired[Optional[Dict["ActorStage", List[Callable]]]], - }, -) -""" -A type, representing anything that can be transformed to pipeline. -It can be Dictionary, containing keys that are present in Pipeline constructor parameters. -""" diff --git a/chatsky/utils/testing/__init__.py b/chatsky/utils/testing/__init__.py index 2e13da083..2081e6dd4 100644 --- a/chatsky/utils/testing/__init__.py +++ b/chatsky/utils/testing/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from .common import is_interactive_mode, check_happy_path, run_interactive_mode -from .toy_script import TOY_SCRIPT, TOY_SCRIPT_ARGS, HAPPY_PATH +from .toy_script import TOY_SCRIPT, TOY_SCRIPT_KWARGS, HAPPY_PATH from .response_comparers import default_comparer try: diff --git a/chatsky/utils/testing/toy_script.py b/chatsky/utils/testing/toy_script.py index 1f0c38dd4..f4327e180 100644 --- a/chatsky/utils/testing/toy_script.py +++ b/chatsky/utils/testing/toy_script.py @@ -39,14 +39,19 @@ :meta hide-value: """ -TOY_SCRIPT_ARGS = (TOY_SCRIPT, ("greeting_flow", "start_node"), ("greeting_flow", "fallback_node")) +TOY_SCRIPT_KWARGS = { + "script": TOY_SCRIPT, + "start_label": ("greeting_flow", "start_node"), + "fallback_label": ("greeting_flow", "fallback_node"), +} """ -Arguments to pass to :py:meth:`~chatsky.pipeline.pipeline.pipeline.Pipeline.from_script` in order to +# There should be a better description of this +Keyword arguments to pass to :py:meth:`~chatsky.pipeline.pipeline.pipeline.Pipeline` in order to use :py:data:`~.TOY_SCRIPT`: .. code-block:: - Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=..., ...) + Pipeline(**TOY_SCRIPT_KWARGS, context_storage=...) :meta hide-value: """ diff --git a/tests/context_storages/test_dbs.py b/tests/context_storages/test_dbs.py index 0d37d2a0d..43820e68f 100644 --- a/tests/context_storages/test_dbs.py +++ b/tests/context_storages/test_dbs.py @@ -33,7 +33,7 @@ from tests.test_utils import get_path_from_tests_to_current_dir from chatsky.pipeline import Pipeline -from chatsky.utils.testing import check_happy_path, TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing import check_happy_path, TOY_SCRIPT_KWARGS, HAPPY_PATH dot_path_to_addon = get_path_from_tests_to_current_dir(__file__, separator=".") @@ -84,7 +84,7 @@ def generic_test(db, testing_context, context_id): assert context_id not in db # test `get` method assert db.get(context_id) is None - pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) + pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) check_happy_path(pipeline, happy_path=HAPPY_PATH) diff --git a/tests/pipeline/test_messenger_interface.py b/tests/pipeline/test_messenger_interface.py index b167a4cc4..545d41c2f 100644 --- a/tests/pipeline/test_messenger_interface.py +++ b/tests/pipeline/test_messenger_interface.py @@ -31,8 +31,8 @@ } } -pipeline = Pipeline.from_script( - SCRIPT, +pipeline = Pipeline( + script=SCRIPT, start_label=("pingpong_flow", "start_node"), fallback_label=("pingpong_flow", "fallback_node"), ) diff --git a/tests/pipeline/test_parallel_processing.py b/tests/pipeline/test_parallel_processing.py index b243c5f4b..4c23e344a 100644 --- a/tests/pipeline/test_parallel_processing.py +++ b/tests/pipeline/test_parallel_processing.py @@ -29,14 +29,14 @@ async def slow_processing(ctx, _): } # test sequential processing - pipeline = Pipeline.from_script(toy_script, start_label=("root", "start"), parallelize_processing=False) + pipeline = Pipeline(script=toy_script, start_label=("root", "start"), parallelize_processing=False) ctx = await pipeline._run_pipeline(Message(), 0) assert ctx.last_response.text == "fast: slow: text" # test parallel processing - pipeline = Pipeline.from_script(toy_script, start_label=("root", "start"), parallelize_processing=True) + pipeline = Pipeline(script=toy_script, start_label=("root", "start"), parallelize_processing=True) ctx = await pipeline._run_pipeline(Message(), 0) diff --git a/tests/pipeline/test_pipeline.py b/tests/pipeline/test_pipeline.py index cb8158984..8d385e2c8 100644 --- a/tests/pipeline/test_pipeline.py +++ b/tests/pipeline/test_pipeline.py @@ -1,24 +1,16 @@ -import importlib - from chatsky.script import Message -from tests.test_utils import get_path_from_tests_to_current_dir from chatsky.pipeline import Pipeline from chatsky.script.core.keywords import RESPONSE, TRANSITIONS import chatsky.script.conditions as cnd -dot_path_to_addon = get_path_from_tests_to_current_dir(__file__, separator=".") - - -def test_pretty_format(): - tutorial_module = importlib.import_module(f"tutorials.{dot_path_to_addon}.5_asynchronous_groups_and_services_full") - tutorial_module.pipeline.pretty_format() - - def test_script_getting_and_setting(): script = {"old_flow": {"": {RESPONSE: lambda _, __: Message(), TRANSITIONS: {"": cnd.true()}}}} - pipeline = Pipeline.from_script(script=script, start_label=("old_flow", "")) + pipeline = Pipeline(script=script, start_label=("old_flow", "")) new_script = {"new_flow": {"": {RESPONSE: lambda _, __: Message(), TRANSITIONS: {"": cnd.false()}}}} - pipeline.set_actor(script=new_script, start_label=("new_flow", "")) - assert list(pipeline.script.script.keys())[0] == list(new_script.keys())[0] + # IDE gives the warning that "Property 'script' cannot be set" + # And yet the test still passes. + pipeline.script = new_script + pipeline.start_label = ("new_flow", "") + assert list(pipeline.script.keys())[0] == list(new_script.keys())[0] diff --git a/tests/pipeline/test_update_ctx_misc.py b/tests/pipeline/test_update_ctx_misc.py index 5a5a5d4c5..4fc2fddb8 100644 --- a/tests/pipeline/test_update_ctx_misc.py +++ b/tests/pipeline/test_update_ctx_misc.py @@ -19,7 +19,7 @@ def condition(ctx, _): } } - pipeline = Pipeline.from_script(toy_script, ("root", "start"), ("root", "failure")) + pipeline = Pipeline(script=toy_script, start_label=("root", "start"), fallback_label=("root", "failure")) ctx = await pipeline._run_pipeline(Message(), 0, update_ctx_misc={"condition": True}) diff --git a/tests/pipeline/test_validation.py b/tests/pipeline/test_validation.py new file mode 100644 index 000000000..c245daed6 --- /dev/null +++ b/tests/pipeline/test_validation.py @@ -0,0 +1,182 @@ +from typing import Callable + +from chatsky.pipeline.pipeline.component import PipelineComponent +from pydantic import ValidationError +import pytest + +from chatsky.pipeline import ( + Pipeline, + Service, + ServiceGroup, + Actor, + ServiceRuntimeInfo, + BeforeHandler, +) +from chatsky.script import Context +from chatsky.utils.testing import TOY_SCRIPT_KWARGS + + +# Looks overly long, we only need one function anyway. +class UserFunctionSamples: + """ + This class contains various examples of user functions along with their signatures. + """ + + @staticmethod + def correct_service_function_1(_: Context): + pass + + @staticmethod + def correct_service_function_2(_: Context, __: Pipeline): + pass + + @staticmethod + def correct_service_function_3(_: Context, __: Pipeline, ___: ServiceRuntimeInfo): + pass + + +# Could make a test for returning an awaitable from a ServiceFunction, ExtraHandlerFunction +class TestServiceValidation: + def test_model_validator(self): + with pytest.raises(ValidationError): + # Can't pass a list to handler, it has to be a single function + Service(handler=[UserFunctionSamples.correct_service_function_2]) + with pytest.raises(ValidationError): + # Can't pass 'None' to handler, it has to be a callable function + # Though I wonder if empty Services should be allowed. + # I see no reason to allow it. + Service() + with pytest.raises(TypeError): + # Python says that two positional arguments were given when only one was expected. + # This happens before Pydantic's validation, so I think there's nothing we can do. + Service(UserFunctionSamples.correct_service_function_1) + with pytest.raises(ValidationError): + # Can't pass 'None' to handler, it has to be a callable function + # Though I wonder if empty Services should be allowed. + # I see no reason to allow it. + Service(handler=Service()) + # But it can work like this. + # A single function gets cast to the right dictionary here. + Service.model_validate(UserFunctionSamples.correct_service_function_1) + + +class TestExtraHandlerValidation: + def test_correct_functions(self): + funcs = [UserFunctionSamples.correct_service_function_1, UserFunctionSamples.correct_service_function_2] + handler = BeforeHandler(functions=funcs) + assert handler.functions == funcs + + def test_single_function(self): + single_function = UserFunctionSamples.correct_service_function_1 + handler = BeforeHandler.model_validate(single_function) + # Checking that a single function is cast to a list within constructor + assert handler.functions == [single_function] + + def test_extra_handler_as_functions(self): + # 'functions' should be a list of ExtraHandlerFunctions, + # but you can pass another ExtraHandler there, because, coincidentally, + # it's a Callable with the right signature. It may be changed later, though. + BeforeHandler.model_validate({"functions": BeforeHandler(functions=[])}) + + def test_wrong_inputs(self): + with pytest.raises(ValidationError): + # 1 is not a callable + BeforeHandler.model_validate(1) + with pytest.raises(ValidationError): + # 'functions' should be a list of ExtraHandlerFunctions + BeforeHandler.model_validate([1, 2, 3]) + + +# Note: I haven't tested components being asynchronous in any way, +# other than in the async pipeline components tutorial. +# It's not a test though. +class TestServiceGroupValidation: + def test_single_service(self): + func = UserFunctionSamples.correct_service_function_2 + group = ServiceGroup(components=Service(handler=func, after_handler=func)) + assert group.components[0].handler == func + assert group.components[0].after_handler.functions[0] == func + # Same, but with model_validate + group = ServiceGroup.model_validate(Service(handler=func, after_handler=func)) + assert group.components[0].handler == func + assert group.components[0].after_handler.functions[0] == func + + def test_several_correct_services(self): + func = UserFunctionSamples.correct_service_function_2 + services = [Service.model_validate(func), Service(handler=func, timeout=10)] + group = ServiceGroup(components=services, timeout=15) + assert group.components == services + assert group.timeout == 15 + assert group.components[0].timeout is None + assert group.components[1].timeout == 10 + + def test_wrong_inputs(self): + with pytest.raises(ValidationError): + # 'components' must be a list of PipelineComponents, wrong type + # Though 123 will be cast to a list + ServiceGroup(components=123) + with pytest.raises(ValidationError): + # The dictionary inside 'components' will check if Actor, Service or ServiceGroup fit the signature, + # but it doesn't fit any of them (required fields are not defined), so it's just a normal dictionary. + ServiceGroup(components={"before_handler": []}) + with pytest.raises(ValidationError): + # The dictionary inside 'components' will try to get cast to Service and will fail. + # 'components' must be a list of PipelineComponents, but it's just a normal dictionary (not a Service). + ServiceGroup(components={"handler": 123}) + + +# Testing of node and script validation for actor exist at script/core/test_actor.py +class TestActorValidation: + def test_toy_script_actor(self): + Actor(**TOY_SCRIPT_KWARGS) + + def test_wrong_inputs(self): + with pytest.raises(ValidationError): + # 'condition_handler' is not an Optional field. + Actor(**TOY_SCRIPT_KWARGS, condition_handler=None) + with pytest.raises(ValidationError): + # 'handlers' is not an Optional field. + Actor(**TOY_SCRIPT_KWARGS, handlers=None) + with pytest.raises(ValidationError): + # 'script' must be either a dict or Script instance. + Actor(script=[], start_label=TOY_SCRIPT_KWARGS["start_label"]) + + +# Can't think of any other tests that aren't done in other tests in this file +class TestPipelineValidation: + def test_correct_inputs(self): + Pipeline(**TOY_SCRIPT_KWARGS) + Pipeline.model_validate(TOY_SCRIPT_KWARGS) + + # Testing if actor is an unchangeable constant throughout the program + def test_cached_property(self): + pipeline = Pipeline(**TOY_SCRIPT_KWARGS) + old_actor_id = id(pipeline.actor) + pipeline.fallback_label = ("greeting_flow", "other_node") + assert old_actor_id == id(pipeline.actor) + + def test_pre_services(self): + with pytest.raises(ValidationError): + # 'pre_services' must be a ServiceGroup + Pipeline(**TOY_SCRIPT_KWARGS, pre_services=123) + + +class CustomPipelineComponent(PipelineComponent): + start_condition: Callable = lambda: True + + def run_component(self, ctx: Context, pipeline: Pipeline): + pass + + +class TestPipelineComponentValidation: + def test_wrong_names(self): + func = UserFunctionSamples.correct_service_function_1 + with pytest.raises(ValidationError): + Service(handler=func, name="bad.name") + with pytest.raises(ValidationError): + Service(handler=func, name="") + + # todo: move this to component tests + def test_name_not_defined(self): + comp = CustomPipelineComponent() + assert comp.computed_name == "noname_service" diff --git a/tests/script/conditions/test_conditions.py b/tests/script/conditions/test_conditions.py index 674ed57d0..f8ae26103 100644 --- a/tests/script/conditions/test_conditions.py +++ b/tests/script/conditions/test_conditions.py @@ -12,7 +12,7 @@ def test_conditions(): failed_ctx = Context() failed_ctx.add_request(Message()) failed_ctx.add_label(label) - pipeline = Pipeline.from_script(script={"flow": {"node": {}}}, start_label=("flow", "node")) + pipeline = Pipeline(script={"flow": {"node": {}}}, start_label=("flow", "node")) assert cnd.exact_match("text")(ctx, pipeline) assert cnd.exact_match(Message("text", misc={}))(ctx, pipeline) diff --git a/tests/script/core/test_actor.py b/tests/script/core/test_actor.py index c2ee2b69b..b840fdc3e 100644 --- a/tests/script/core/test_actor.py +++ b/tests/script/core/test_actor.py @@ -1,6 +1,6 @@ # %% import pytest -from chatsky.pipeline import Pipeline +from chatsky.pipeline import Pipeline, ComponentExecutionState from chatsky.script import ( TRANSITIONS, RESPONSE, @@ -51,47 +51,44 @@ def raised_response(ctx: Context, pipeline): async def test_actor(): try: # fail of start label - Pipeline.from_script({"flow": {"node1": {}}}, start_label=("flow1", "node1")) + Pipeline(script={"flow": {"node1": {}}}, start_label=("flow1", "node1")) raise Exception("can not be passed: fail of start label") except ValueError: pass try: # fail of fallback label - Pipeline.from_script({"flow": {"node1": {}}}, start_label=("flow", "node1"), fallback_label=("flow1", "node1")) + Pipeline(script={"flow": {"node1": {}}}, start_label=("flow", "node1"), fallback_label=("flow1", "node1")) raise Exception("can not be passed: fail of fallback label") except ValueError: pass try: # fail of missing node - Pipeline.from_script({"flow": {"node1": {TRANSITIONS: {"miss_node1": true()}}}}, start_label=("flow", "node1")) + Pipeline(script={"flow": {"node1": {TRANSITIONS: {"miss_node1": true()}}}}, start_label=("flow", "node1")) raise Exception("can not be passed: fail of missing node") except ValueError: pass try: # fail of response returned Callable - pipeline = Pipeline.from_script( - {"flow": {"node1": {RESPONSE: lambda c, a: lambda x: 1, TRANSITIONS: {repeat(): true()}}}}, + pipeline = Pipeline( + script={"flow": {"node1": {RESPONSE: lambda c, a: lambda x: 1, TRANSITIONS: {repeat(): true()}}}}, start_label=("flow", "node1"), ) ctx = Context() - await pipeline.actor(pipeline, ctx) + await pipeline.actor(ctx, pipeline) + assert pipeline.actor.get_state(ctx) is not ComponentExecutionState.FAILED raise Exception("can not be passed: fail of response returned Callable") - except ValueError: + except AssertionError: pass # empty ctx stability - pipeline = Pipeline.from_script( - {"flow": {"node1": {TRANSITIONS: {"node1": true()}}}}, start_label=("flow", "node1") - ) + pipeline = Pipeline(script={"flow": {"node1": {TRANSITIONS: {"node1": true()}}}}, start_label=("flow", "node1")) ctx = Context() - await pipeline.actor(pipeline, ctx) + await pipeline.actor(ctx, pipeline) # fake label stability - pipeline = Pipeline.from_script( - {"flow": {"node1": {TRANSITIONS: {fake_label: true()}}}}, start_label=("flow", "node1") - ) + pipeline = Pipeline(script={"flow": {"node1": {TRANSITIONS: {fake_label: true()}}}}, start_label=("flow", "node1")) ctx = Context() - await pipeline.actor(pipeline, ctx) + await pipeline.actor(ctx, pipeline) limit_errors = {} @@ -195,7 +192,7 @@ async def test_call_limit(): }, } # script = {"flow": {"node1": {TRANSITIONS: {"node1": true()}}}} - pipeline = Pipeline.from_script(script=script, start_label=("flow1", "node1")) + pipeline = Pipeline(script=script, start_label=("flow1", "node1")) for i in range(4): await pipeline._run_pipeline(Message("req1"), 0) if limit_errors: diff --git a/tests/script/core/test_normalization.py b/tests/script/core/test_normalization.py index cda6b6b36..347486949 100644 --- a/tests/script/core/test_normalization.py +++ b/tests/script/core/test_normalization.py @@ -28,7 +28,7 @@ def std_func(ctx, pipeline): def create_env() -> Tuple[Context, Pipeline]: ctx = Context() script = {"flow": {"node1": {TRANSITIONS: {repeat(): true()}, RESPONSE: Message("response")}}} - pipeline = Pipeline.from_script(script=script, start_label=("flow", "node1"), fallback_label=("flow", "node1")) + pipeline = Pipeline(script=script, start_label=("flow", "node1"), fallback_label=("flow", "node1")) ctx.add_request(Message("text")) return ctx, pipeline diff --git a/tests/script/labels/test_labels.py b/tests/script/labels/test_labels.py index a03937999..fcb109adb 100644 --- a/tests/script/labels/test_labels.py +++ b/tests/script/labels/test_labels.py @@ -6,7 +6,7 @@ def test_labels(): ctx = Context() - pipeline = Pipeline.from_script( + pipeline = Pipeline( script={"flow": {"node1": {}, "node2": {}, "node3": {}}, "service": {"start": {}, "fallback": {}}}, start_label=("service", "start"), fallback_label=("service", "fallback"), @@ -36,7 +36,7 @@ def test_labels(): assert backward(99, cyclicality_flag=False)(ctx, pipeline) == ("service", "fallback", 99) ctx = Context() ctx.add_label(["flow", "node2"]) - pipeline = Pipeline.from_script( + pipeline = Pipeline( script={"flow": {"node1": {}}, "service": {"start": {}, "fallback": {}}}, start_label=("service", "start"), fallback_label=("service", "fallback"), diff --git a/tests/script/responses/test_responses.py b/tests/script/responses/test_responses.py index 53157f610..c281a9b31 100644 --- a/tests/script/responses/test_responses.py +++ b/tests/script/responses/test_responses.py @@ -6,6 +6,6 @@ def test_response(): ctx = Context() - pipeline = Pipeline.from_script(script={"flow": {"node": {}}}, start_label=("flow", "node")) + pipeline = Pipeline(script={"flow": {"node": {}}}, start_label=("flow", "node")) for _ in range(10): assert choice(["text1", "text2"])(ctx, pipeline) in ["text1", "text2"] diff --git a/tests/slots/conftest.py b/tests/slots/conftest.py index 5d94cf63d..8539b013a 100644 --- a/tests/slots/conftest.py +++ b/tests/slots/conftest.py @@ -17,7 +17,7 @@ def patch_exception_equality(monkeypatch): @pytest.fixture(scope="function") def pipeline(): script = {"flow": {"node": {RESPONSE: Message(), TRANSITIONS: {"node": cnd.true()}}}} - pipeline = Pipeline.from_script(script=script, start_label=("flow", "node")) + pipeline = Pipeline(script=script, start_label=("flow", "node")) return pipeline diff --git a/tests/stats/test_defaults.py b/tests/stats/test_defaults.py index 43f3d4fe5..a770fdc5a 100644 --- a/tests/stats/test_defaults.py +++ b/tests/stats/test_defaults.py @@ -21,7 +21,7 @@ ], ) async def test_get_current_label(context: Context, expected: set): - pipeline = Pipeline.from_script({"greeting_flow": {"start_node": {}}}, ("greeting_flow", "start_node")) + pipeline = Pipeline(script={"greeting_flow": {"start_node": {}}}, start_label=("greeting_flow", "start_node")) runtime_info = ExtraHandlerRuntimeInfo( func=lambda x: x, stage="BEFORE", diff --git a/tutorials/context_storages/1_basics.py b/tutorials/context_storages/1_basics.py index ce5d484a5..4ea9c9d51 100644 --- a/tutorials/context_storages/1_basics.py +++ b/tutorials/context_storages/1_basics.py @@ -23,14 +23,14 @@ is_interactive_mode, run_interactive_mode, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH pathlib.Path("dbs").mkdir(exist_ok=True) db = context_storage_factory("json://dbs/file.json") # db = context_storage_factory("pickle://dbs/file.pkl") # db = context_storage_factory("shelve://dbs/file") -pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) +pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) if __name__ == "__main__": check_happy_path(pipeline, HAPPY_PATH) diff --git a/tutorials/context_storages/2_postgresql.py b/tutorials/context_storages/2_postgresql.py index 8ca07e95b..4124c4799 100644 --- a/tutorials/context_storages/2_postgresql.py +++ b/tutorials/context_storages/2_postgresql.py @@ -25,7 +25,7 @@ is_interactive_mode, run_interactive_mode, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH # %% @@ -37,7 +37,7 @@ db = context_storage_factory(db_uri) -pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) +pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) # %% diff --git a/tutorials/context_storages/3_mongodb.py b/tutorials/context_storages/3_mongodb.py index a68512ab4..0b2919e73 100644 --- a/tutorials/context_storages/3_mongodb.py +++ b/tutorials/context_storages/3_mongodb.py @@ -24,7 +24,7 @@ is_interactive_mode, run_interactive_mode, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH # %% @@ -35,7 +35,7 @@ ) db = context_storage_factory(db_uri) -pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) +pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) # %% diff --git a/tutorials/context_storages/4_redis.py b/tutorials/context_storages/4_redis.py index 51dfee008..5fe215ddf 100644 --- a/tutorials/context_storages/4_redis.py +++ b/tutorials/context_storages/4_redis.py @@ -24,7 +24,7 @@ is_interactive_mode, run_interactive_mode, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH # %% @@ -34,7 +34,7 @@ db = context_storage_factory(db_uri) -pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) +pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) # %% diff --git a/tutorials/context_storages/5_mysql.py b/tutorials/context_storages/5_mysql.py index b52a5c3f6..b9f9d01a0 100644 --- a/tutorials/context_storages/5_mysql.py +++ b/tutorials/context_storages/5_mysql.py @@ -25,7 +25,7 @@ is_interactive_mode, run_interactive_mode, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH # %% @@ -37,7 +37,7 @@ db = context_storage_factory(db_uri) -pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) +pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) # %% diff --git a/tutorials/context_storages/6_sqlite.py b/tutorials/context_storages/6_sqlite.py index 76ede50e8..b9e8ea8a9 100644 --- a/tutorials/context_storages/6_sqlite.py +++ b/tutorials/context_storages/6_sqlite.py @@ -28,7 +28,7 @@ is_interactive_mode, run_interactive_mode, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH # %% @@ -41,7 +41,7 @@ db = context_storage_factory(db_uri) -pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) +pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) # %% diff --git a/tutorials/context_storages/7_yandex_database.py b/tutorials/context_storages/7_yandex_database.py index 294744cb4..be5ac7a69 100644 --- a/tutorials/context_storages/7_yandex_database.py +++ b/tutorials/context_storages/7_yandex_database.py @@ -24,7 +24,7 @@ run_interactive_mode, is_interactive_mode, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT_ARGS, HAPPY_PATH +from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH # %% @@ -42,7 +42,7 @@ ) db = context_storage_factory(db_uri) -pipeline = Pipeline.from_script(*TOY_SCRIPT_ARGS, context_storage=db) +pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) # %% diff --git a/tutorials/messengers/telegram/1_basic.py b/tutorials/messengers/telegram/1_basic.py index cb050bb89..a7379a91d 100644 --- a/tutorials/messengers/telegram/1_basic.py +++ b/tutorials/messengers/telegram/1_basic.py @@ -70,7 +70,7 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) # %% -pipeline = Pipeline.from_script( +pipeline = Pipeline( script=script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), diff --git a/tutorials/messengers/telegram/2_attachments.py b/tutorials/messengers/telegram/2_attachments.py index 93c8d233d..461710169 100644 --- a/tutorials/messengers/telegram/2_attachments.py +++ b/tutorials/messengers/telegram/2_attachments.py @@ -258,7 +258,7 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) # %% -pipeline = Pipeline.from_script( +pipeline = Pipeline( script=script, start_label=("main_flow", "start_node"), fallback_label=("main_flow", "fallback_node"), diff --git a/tutorials/messengers/telegram/3_advanced.py b/tutorials/messengers/telegram/3_advanced.py index c10624db9..bed4da369 100644 --- a/tutorials/messengers/telegram/3_advanced.py +++ b/tutorials/messengers/telegram/3_advanced.py @@ -247,7 +247,7 @@ async def hash_data_attachment_request(ctx: Context, pipe: Pipeline) -> Message: # %% -pipeline = Pipeline.from_script( +pipeline = Pipeline( script=script, start_label=("main_flow", "start_node"), fallback_label=("main_flow", "fallback_node"), diff --git a/tutorials/messengers/web_api_interface/1_fastapi.py b/tutorials/messengers/web_api_interface/1_fastapi.py index a3fed68eb..ef07009ed 100644 --- a/tutorials/messengers/web_api_interface/1_fastapi.py +++ b/tutorials/messengers/web_api_interface/1_fastapi.py @@ -20,7 +20,7 @@ from chatsky.messengers.common.interface import CallbackMessengerInterface from chatsky.script import Message from chatsky.pipeline import Pipeline -from chatsky.utils.testing import TOY_SCRIPT_ARGS, is_interactive_mode +from chatsky.utils.testing import TOY_SCRIPT_KWARGS, is_interactive_mode import uvicorn from pydantic import BaseModel @@ -83,8 +83,8 @@ # %% messenger_interface = CallbackMessengerInterface() # CallbackMessengerInterface instantiating the dedicated messenger interface -pipeline = Pipeline.from_script( - *TOY_SCRIPT_ARGS, messenger_interface=messenger_interface +pipeline = Pipeline( + **TOY_SCRIPT_KWARGS, messenger_interface=messenger_interface ) diff --git a/tutorials/messengers/web_api_interface/2_websocket_chat.py b/tutorials/messengers/web_api_interface/2_websocket_chat.py index 7163899c8..af766d1f5 100644 --- a/tutorials/messengers/web_api_interface/2_websocket_chat.py +++ b/tutorials/messengers/web_api_interface/2_websocket_chat.py @@ -27,7 +27,7 @@ from chatsky.messengers.common.interface import CallbackMessengerInterface from chatsky.script import Message from chatsky.pipeline import Pipeline -from chatsky.utils.testing import TOY_SCRIPT_ARGS, is_interactive_mode +from chatsky.utils.testing import TOY_SCRIPT_KWARGS, is_interactive_mode import uvicorn from fastapi import FastAPI, WebSocket, WebSocketDisconnect @@ -36,8 +36,8 @@ # %% messenger_interface = CallbackMessengerInterface() -pipeline = Pipeline.from_script( - *TOY_SCRIPT_ARGS, messenger_interface=messenger_interface +pipeline = Pipeline( + **TOY_SCRIPT_KWARGS, messenger_interface=messenger_interface ) diff --git a/tutorials/pipeline/1_basics.py b/tutorials/pipeline/1_basics.py index fe285e15e..898558d91 100644 --- a/tutorials/pipeline/1_basics.py +++ b/tutorials/pipeline/1_basics.py @@ -22,16 +22,16 @@ is_interactive_mode, HAPPY_PATH, TOY_SCRIPT, - TOY_SCRIPT_ARGS, + TOY_SCRIPT_KWARGS, ) # %% [markdown] """ `Pipeline` is an object, that automates script execution and context management. -`from_script` method can be used to create +It's constructor method can be used to create a pipeline of the most basic structure: -"preprocessors -> actor -> postprocessors" +"pre-services -> actor -> post-services" as well as to define `context_storage` and `messenger_interface`. Actor is a component of :py:class:`.Pipeline`, that contains the :py:class:`.Script` and handles it. It is responsible for processing @@ -42,7 +42,7 @@ Here only required parameters are provided to pipeline. `context_storage` will default to simple Python dict and `messenger_interface` will never be used. -pre- and postprocessors lists are empty. +pre- and post-services lists are empty. `Pipeline` object can be called with user input as first argument and dialog id (any immutable object). This call will return `Context`, @@ -50,8 +50,8 @@ """ # %% -pipeline = Pipeline.from_script( - TOY_SCRIPT, +pipeline = Pipeline( + script=TOY_SCRIPT, # Pipeline script object, defined in `chatsky.utils.testing.toy_script` start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), @@ -61,15 +61,15 @@ # %% [markdown] """ For the sake of brevity, other tutorials -might use `TOY_SCRIPT_ARGS` to initialize pipeline: +might use `TOY_SCRIPT_KWARGS` (keyword arguments) to initialize pipeline: """ # %% -assert TOY_SCRIPT_ARGS == ( - TOY_SCRIPT, - ("greeting_flow", "start_node"), - ("greeting_flow", "fallback_node"), -) +assert TOY_SCRIPT_KWARGS == { + "script": TOY_SCRIPT, + "start_label": ("greeting_flow", "start_node"), + "fallback_label": ("greeting_flow", "fallback_node"), +} # %% diff --git a/tutorials/pipeline/2_pre_and_post_processors.py b/tutorials/pipeline/2_pre_and_post_processors.py index 2f418d41a..ec45af5be 100644 --- a/tutorials/pipeline/2_pre_and_post_processors.py +++ b/tutorials/pipeline/2_pre_and_post_processors.py @@ -23,7 +23,7 @@ check_happy_path, is_interactive_mode, HAPPY_PATH, - TOY_SCRIPT_ARGS, + TOY_SCRIPT_KWARGS, ) logger = logging.getLogger(__name__) @@ -31,11 +31,11 @@ # %% [markdown] """ -When Pipeline is created with `from_script` method, additional pre- -and postprocessors can be defined. -These can be any `ServiceBuilder` objects (defined in `types` module) -- callables, objects or dicts. -They are being turned into special `Service` objects (see tutorial 3), +When Pipeline is created, additional pre- +and post-services can be defined. +These can be any callables, certain objects or dicts. +They are being turned into special `Service` or `ServiceGroup` objects +(see tutorial 3), that will be run before or after `Actor` respectively. These services can be used to access external APIs, annotate user input, etc. @@ -65,8 +65,8 @@ def pong_processor(ctx: Context): # %% -pipeline = Pipeline.from_script( - *TOY_SCRIPT_ARGS, +pipeline = Pipeline( + **TOY_SCRIPT_KWARGS, context_storage={}, # `context_storage` - a dictionary or # a `DBContextStorage` instance, # a place to store dialog contexts diff --git a/tutorials/pipeline/3_pipeline_dict_with_services_basic.py b/tutorials/pipeline/3_pipeline_dict_with_services_basic.py index a4ad6507e..f339771a7 100644 --- a/tutorials/pipeline/3_pipeline_dict_with_services_basic.py +++ b/tutorials/pipeline/3_pipeline_dict_with_services_basic.py @@ -8,8 +8,8 @@ Here, %mddoclink(api,pipeline.service.service,Service) class, that can be used for pre- and postprocessing of messages is shown. -Pipeline's %mddoclink(api,pipeline.pipeline.pipeline,Pipeline.from_dict) -static method is used for pipeline creation (from dictionary). +%mddoclink(api,pipeline.pipeline.pipeline,Pipeline)'s +constructor method is used for pipeline creation (directly or from dictionary). """ # %pip install chatsky @@ -17,7 +17,7 @@ # %% import logging -from chatsky.pipeline import Service, Pipeline, ACTOR +from chatsky.pipeline import Service, Pipeline from chatsky.utils.testing.common import ( check_happy_path, @@ -31,25 +31,44 @@ # %% [markdown] """ -When Pipeline is created using `from_dict` method, -pipeline should be defined as a dictionary. -It should contain `components` - a `ServiceGroupBuilder` object, -basically a list of `ServiceBuilder` or `ServiceGroupBuilder` objects, -see tutorial 4. - -On pipeline execution services from `components` +When Pipeline is created using it's constructor method or +Pydantic's `model_validate` method, +`Pipeline` should be defined as a dictionary of a particular structure, +which must contain `script`, `start_label` and `fallback_label`, +see `Script` tutorials. + +Optional Pipeline parameters: +* `messenger_interface` - `MessengerInterface` instance, + is used to connect to channel and transfer IO to user. +* `context_storage` - Place to store dialog contexts + (dictionary or a `DBContextStorage` instance). +* `pre-services` - A `ServiceGroup` object, + basically a list of `Service` objects or more `ServiceGroup` objects, + see tutorial 4. +* `post-services` - A `ServiceGroup` object, + basically a list of `Service` objects or more `ServiceGroup` objects, + see tutorial 4. +* `before_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. + See tutorials 6 and 7. +* `after_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. + See tutorials 6 and 7. +* `timeout` - Pipeline timeout, see tutorial 5. + +On pipeline execution services from +`components` = 'pre-services' + actor + 'post-services' list are run without difference between pre- and postprocessors. -Actor constant "ACTOR" is required to be passed as one of the services. -`ServiceBuilder` object can be defined either with callable -(see tutorial 2) or with dict / object. -It should contain `handler` - a `ServiceBuilder` object. +`Service` object can be defined either with callable +(see tutorial 2) or with `Service` constructor / dict. +It must contain `handler` - a callable (function). Not only Pipeline can be run using `__call__` method, for most cases `run` method should be used. It starts pipeline asynchronously and connects to provided messenger interface. -Here, the pipeline contains 4 services, -defined in 4 different ways with different signatures. +Here, the pipeline contains 3 services, +defined in 3 different ways with different signatures. """ @@ -76,20 +95,21 @@ def postprocess(_): "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ + "pre_services": [ { "handler": prepreprocess, + "name": "prepreprocessor", }, preprocess, - ACTOR, - Service( - handler=postprocess, - ), ], + "post_services": Service(handler=postprocess, name="postprocessor"), } # %% -pipeline = Pipeline.from_dict(pipeline_dict) +pipeline = Pipeline(**pipeline_dict) +# or +# pipeline = Pipeline.model_validate(pipeline_dict) + if __name__ == "__main__": check_happy_path(pipeline, HAPPY_PATH) diff --git a/tutorials/pipeline/3_pipeline_dict_with_services_full.py b/tutorials/pipeline/3_pipeline_dict_with_services_full.py index 2759a502a..ad17281b0 100644 --- a/tutorials/pipeline/3_pipeline_dict_with_services_full.py +++ b/tutorials/pipeline/3_pipeline_dict_with_services_full.py @@ -20,7 +20,7 @@ from chatsky.script import Context from chatsky.messengers.console import CLIMessengerInterface -from chatsky.pipeline import Service, Pipeline, ServiceRuntimeInfo, ACTOR +from chatsky.pipeline import Service, Pipeline, ServiceRuntimeInfo from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, @@ -34,46 +34,45 @@ # %% [markdown] """ -When Pipeline is created using `from_dict` method, -pipeline should be defined as `PipelineBuilder` objects -(defined in `types` module). -These objects are dictionaries of particular structure: +When Pipeline is created using Pydantic's `model_validate` method +or `Pipeline`'s constructor method, pipeline should be +defined as a dictionary of a particular structure: * `messenger_interface` - `MessengerInterface` instance, is used to connect to channel and transfer IO to user. * `context_storage` - Place to store dialog contexts (dictionary or a `DBContextStorage` instance). -* `components` (required) - A `ServiceGroupBuilder` object, - basically a list of `ServiceBuilder` or `ServiceGroupBuilder` objects, +* `pre-services` - A `ServiceGroup` object, + basically a list of `Service` objects or more `ServiceGroup` objects, see tutorial 4. -* `before_handler` - a list of `ExtraHandlerFunction` objects, - `ExtraHandlerBuilder` objects and lists of them. +* `post-services` - A `ServiceGroup` object, + basically a list of `Service` objects or more `ServiceGroup` objects, + see tutorial 4. +* `before_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. See tutorials 6 and 7. -* `after_handler` - a list of `ExtraHandlerFunction` objects, - `ExtraHandlerBuilder` objects and lists of them. +* `after_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. See tutorials 6 and 7. * `timeout` - Pipeline timeout, see tutorial 5. * `optimization_warnings` - Whether pipeline asynchronous structure should be checked during initialization, see tutorial 5. -On pipeline execution services from `components` list are run -without difference between pre- and postprocessors. -If "ACTOR" constant is not found among `components` pipeline creation fails. -There can be only one "ACTOR" constant in the pipeline. -`ServiceBuilder` object can be defined either with callable (see tutorial 2) or -with dict of structure / object with following constructor arguments: - -* `handler` (required) - ServiceBuilder, - if handler is an object or a dict itself, - it will be used instead of base ServiceBuilder. - NB! Fields of nested ServiceBuilder will be overridden - by defined fields of the base ServiceBuilder. -* `before_handler` - a list of `ExtraHandlerFunction` objects, - `ExtraHandlerBuilder` objects and lists of them. +On pipeline execution services from +`components` = 'pre-services' + actor + 'post-services' +list are run without difference between pre- and postprocessors. +`Service` object can be defined either with callable +(see tutorial 2) or with dict of structure / `Service` object + with following constructor arguments: + + +* `handler` (required) - ServiceFunction. +* `before_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. See tutorials 6 and 7. -* `after_handler` - a list of `ExtraHandlerFunction` objects, - `ExtraHandlerBuilder` objects and lists of them. +* `after_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. See tutorials 6 and 7. * `timeout` - service timeout, see tutorial 5. * `asynchronous` - whether or not this service _should_ be asynchronous @@ -88,11 +87,10 @@ for most cases `run` method should be used. It starts pipeline asynchronously and connects to provided messenger interface. -Here pipeline contains 4 services, -defined in 4 different ways with different signatures. +Here pipeline contains 3 services, +defined in 3 different ways with different signatures. First two of them write sample feature detection data to `ctx.misc`. The first uses a constant expression and the second fetches from `example.com`. -Third one is "ACTOR" constant (it acts like a _special_ service here). Final service logs `ctx.misc` dict. """ @@ -151,27 +149,19 @@ def postprocess(ctx: Context, pl: Pipeline): # `prompt_request` - a string that will be displayed before user input # `prompt_response` - an output prefix string "context_storage": {}, - "components": [ + "pre_services": [ { - "handler": { - "handler": prepreprocess, - "name": "silly_service_name", - }, + "handler": prepreprocess, "name": "preprocessor", - }, # This service will be named `preprocessor` - # handler name will be overridden + }, preprocess, - ACTOR, - Service( - handler=postprocess, - name="postprocessor", - ), ], + "post_services": Service(handler=postprocess, name="postprocessor"), } # %% -pipeline = Pipeline.from_dict(pipeline_dict) +pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": check_happy_path(pipeline, HAPPY_PATH) diff --git a/tutorials/pipeline/4_groups_and_conditions_basic.py b/tutorials/pipeline/4_groups_and_conditions_basic.py index d791c0e11..bd560695f 100644 --- a/tutorials/pipeline/4_groups_and_conditions_basic.py +++ b/tutorials/pipeline/4_groups_and_conditions_basic.py @@ -21,7 +21,6 @@ not_condition, service_successful_condition, ServiceRuntimeInfo, - ACTOR, ) from chatsky.utils.testing.common import ( @@ -37,10 +36,10 @@ # %% [markdown] """ Pipeline can contain not only single services, but also service groups. -Service groups can be defined as `ServiceGroupBuilder` objects: - lists of `ServiceBuilders` and `ServiceGroupBuilders` or objects. -The objects should contain `components` - -a `ServiceBuilder` and `ServiceGroupBuilder` object list. +Service groups can be defined as `ServiceGroup` objects: + lists of `Service` or more `ServiceGroup` objects. +`ServiceGroup` objects should contain `components` - +a list of `Service` and `ServiceGroup` objects. To receive serialized information about service, service group or pipeline a property `info_dict` can be used, @@ -96,12 +95,10 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - Service( - handler=always_running_service, - name="always_running_service", - ), - ACTOR, + "pre_services": Service( + handler=always_running_service, name="always_running_service" + ), + "post_services": [ Service( handler=never_running_service, start_condition=not_condition( @@ -117,7 +114,7 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): # %% -pipeline = Pipeline.from_dict(pipeline_dict) +pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": check_happy_path(pipeline, HAPPY_PATH) diff --git a/tutorials/pipeline/4_groups_and_conditions_full.py b/tutorials/pipeline/4_groups_and_conditions_full.py index b0190b54d..f185b18be 100644 --- a/tutorials/pipeline/4_groups_and_conditions_full.py +++ b/tutorials/pipeline/4_groups_and_conditions_full.py @@ -22,7 +22,6 @@ service_successful_condition, all_condition, ServiceRuntimeInfo, - ACTOR, ) from chatsky.utils.testing.common import ( @@ -34,23 +33,29 @@ logger = logging.getLogger(__name__) - # %% [markdown] """ Pipeline can contain not only single services, but also service groups. -Service groups can be defined as lists of `ServiceBuilders` +Service groups can be defined as `ServiceGroup` objects: + lists of `Service` or more `ServiceGroup` objects. +`ServiceGroup` objects should contain `components` - +a list of `Service` and `ServiceGroup` objects. + +Pipeline can contain not only single services, but also service groups. +Service groups can be defined as lists of `Service` + or more `ServiceGroup` objects. (in fact, all of the pipeline services are combined into root service group named "pipeline"). Alternatively, the groups can be defined as objects with following constructor arguments: -* `components` (required) - A list of `ServiceBuilder` objects, - `ServiceGroupBuilder` objects and lists of them. -* `before_handler` - a list of `ExtraHandlerFunction` objects, - `ExtraHandlerBuilder` objects and lists of them. +* `components` (required) - A list of `Service` objects, + `ServiceGroup` objects. +* `before_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. See tutorials 6 and 7. -* `after_handler` - a list of `ExtraHandlerFunction` objects, - `ExtraHandlerBuilder` objects and lists of them. +* `after_handler` - a list of `ExtraHandlerFunction` objects or + a `ComponentExtraHandler` object. See tutorials 6 and 7. * `timeout` - Pipeline timeout, see tutorial 5. * `asynchronous` - Whether or not this service group _should_ be asynchronous @@ -77,12 +82,11 @@ If no name is specified for a service or service group, the name will be generated according to the following rules: -1. If service's handler is an Actor, service will be named 'actor'. -2. If service's handler is callable, +1. If service's handler is callable, service will be named callable. -3. Service group will be named 'service_group'. -4. Otherwise, it will be named 'noname_service'. -5. After that an index will be added to service name. +2. Service group will be named 'service_group'. +3. Otherwise, it will be named 'noname_service'. +4. After that an index will be added to service name. To receive serialized information about service, service group or pipeline a property `info_dict` can be used, @@ -136,7 +140,6 @@ Function that returns `True` if any of the given `functions` (condition functions) return `True`. -NB! Actor service ALWAYS runs unconditionally. Here there are two conditionally executed services: a service named `running_service` is executed @@ -170,15 +173,14 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - [ - simple_service, # This simple service - # will be named `simple_service_0` - simple_service, # This simple service - # will be named `simple_service_1` - ], # Despite this is the unnamed service group in the root - # service group, it will be named `service_group_0` - ACTOR, + "pre_services": [ + simple_service, # This simple service + # will be named `simple_service_0` + simple_service, # This simple service + # will be named `simple_service_1` + ], # Despite this is the unnamed service group in the root + # service group, it will be named `service_group_0` + "post_services": [ ServiceGroup( name="named_group", components=[ @@ -211,7 +213,7 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): } # %% -pipeline = Pipeline.from_dict(pipeline_dict) +pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) diff --git a/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py b/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py index 9876290e9..bf82879a4 100644 --- a/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py +++ b/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py @@ -14,7 +14,7 @@ # %% import asyncio -from chatsky.pipeline import Pipeline, ACTOR +from chatsky.pipeline import Pipeline from chatsky.utils.testing.common import ( is_interactive_mode, @@ -50,14 +50,11 @@ async def time_consuming_service(_): "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - [time_consuming_service for _ in range(0, 10)], - ACTOR, - ], + "pre_services": [time_consuming_service for _ in range(0, 10)], } # %% -pipeline = Pipeline.from_dict(pipeline_dict) +pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": check_happy_path(pipeline, HAPPY_PATH) diff --git a/tutorials/pipeline/5_asynchronous_groups_and_services_full.py b/tutorials/pipeline/5_asynchronous_groups_and_services_full.py index fbe707ff7..ef2dacb32 100644 --- a/tutorials/pipeline/5_asynchronous_groups_and_services_full.py +++ b/tutorials/pipeline/5_asynchronous_groups_and_services_full.py @@ -19,10 +19,8 @@ import logging import urllib.request +from chatsky.pipeline import ServiceGroup, Pipeline, ServiceRuntimeInfo from chatsky.script import Context - -from chatsky.pipeline import ServiceGroup, Pipeline, ServiceRuntimeInfo, ACTOR - from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, @@ -36,7 +34,7 @@ """ Services and service groups can be synchronous and asynchronous. In synchronous service groups services are executed consequently, - some of them (`ACTOR`) can even return `Context` object, + some of them can even return `Context` object, modifying it. In asynchronous service groups all services are executed simultaneously and should not return anything, @@ -54,7 +52,6 @@ the service becomes asynchronous, and if set, it is used instead. If service can not be asynchronous, but is marked asynchronous, an exception is thrown. -ACTOR service is asynchronous. The timeout field only works for asynchronous services and service groups. If service execution takes more time than timeout, @@ -78,7 +75,8 @@ it logs HTTPS requests (from 1 to 15), running simultaneously, in random order. Service group `pipeline` can't be asynchronous because -`balanced_group` and ACTOR are synchronous. +`balanced_group` and `Actor` are synchronous. +(`Actor` is added into `Pipeline`'s 'components' during it's creation) """ @@ -127,27 +125,26 @@ def context_printing_service(ctx: Context): "fallback_label": ("greeting_flow", "fallback_node"), "optimization_warnings": True, # There are no warnings - pipeline is well-optimized - "components": [ - ServiceGroup( - name="balanced_group", - asynchronous=False, - components=[ - simple_asynchronous_service, - ServiceGroup( - timeout=0.02, - components=[time_consuming_service for _ in range(0, 6)], - ), - simple_asynchronous_service, - ], - ), - ACTOR, + "pre_services": ServiceGroup( + name="balanced_group", + requested_async_flag=False, + components=[ + simple_asynchronous_service, + ServiceGroup( + timeout=0.02, + components=[time_consuming_service for _ in range(0, 6)], + ), + simple_asynchronous_service, + ], + ), + "post_services": [ [meta_web_querying_service(photo) for photo in range(1, 16)], context_printing_service, ], } # %% -pipeline = Pipeline.from_dict(pipeline_dict) +pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": check_happy_path(pipeline, HAPPY_PATH) diff --git a/tutorials/pipeline/6_extra_handlers_basic.py b/tutorials/pipeline/6_extra_handlers_basic.py index 11fe52cfe..4de439271 100644 --- a/tutorials/pipeline/6_extra_handlers_basic.py +++ b/tutorials/pipeline/6_extra_handlers_basic.py @@ -18,15 +18,12 @@ import random from datetime import datetime -from chatsky.script import Context - from chatsky.pipeline import ( Pipeline, ServiceGroup, ExtraHandlerRuntimeInfo, - ACTOR, ) - +from chatsky.script import Context from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, @@ -82,41 +79,38 @@ def logging_service(ctx: Context): "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - ServiceGroup( - before_handler=[collect_timestamp_before], - after_handler=[collect_timestamp_after], - components=[ - { - "handler": heavy_service, - "before_handler": [collect_timestamp_before], - "after_handler": [collect_timestamp_after], - }, - { - "handler": heavy_service, - "before_handler": [collect_timestamp_before], - "after_handler": [collect_timestamp_after], - }, - { - "handler": heavy_service, - "before_handler": [collect_timestamp_before], - "after_handler": [collect_timestamp_after], - }, - { - "handler": heavy_service, - "before_handler": [collect_timestamp_before], - "after_handler": [collect_timestamp_after], - }, - { - "handler": heavy_service, - "before_handler": [collect_timestamp_before], - "after_handler": [collect_timestamp_after], - }, - ], - ), - ACTOR, - logging_service, - ], + "pre_services": ServiceGroup( + before_handler=[collect_timestamp_before], + after_handler=[collect_timestamp_after], + components=[ + { + "handler": heavy_service, + "before_handler": [collect_timestamp_before], + "after_handler": [collect_timestamp_after], + }, + { + "handler": heavy_service, + "before_handler": [collect_timestamp_before], + "after_handler": [collect_timestamp_after], + }, + { + "handler": heavy_service, + "before_handler": [collect_timestamp_before], + "after_handler": [collect_timestamp_after], + }, + { + "handler": heavy_service, + "before_handler": [collect_timestamp_before], + "after_handler": [collect_timestamp_after], + }, + { + "handler": heavy_service, + "before_handler": [collect_timestamp_before], + "after_handler": [collect_timestamp_after], + }, + ], + ), + "post_services": logging_service, } # %% diff --git a/tutorials/pipeline/6_extra_handlers_full.py b/tutorials/pipeline/6_extra_handlers_full.py index dbc717b59..a3e2c5a26 100644 --- a/tutorials/pipeline/6_extra_handlers_full.py +++ b/tutorials/pipeline/6_extra_handlers_full.py @@ -17,17 +17,15 @@ from datetime import datetime import psutil -from chatsky.script import Context from chatsky.pipeline import ( Pipeline, ServiceGroup, - to_service, ExtraHandlerRuntimeInfo, ServiceRuntimeInfo, - ACTOR, + to_service, ) - +from chatsky.script import Context from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, @@ -74,6 +72,8 @@ 2. (Services only) `to_service` decorator - transforms function to service with extra handlers from `before_handler` and `after_handler` arguments. +3. Using `add_extra_handler` function of `PipelineComponent` Example: +component.add_extra_handler(GlobalExtraHandlerType.AFTER, get_service_state) Here 5 `heavy_service`s fill big amounts of memory with random numbers. Their runtime stats are captured and displayed by extra services, @@ -172,15 +172,12 @@ def logging_service(ctx: Context, _, info: ServiceRuntimeInfo): "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - ServiceGroup( - before_handler=[time_measure_before_handler], - after_handler=[time_measure_after_handler], - components=[heavy_service for _ in range(0, 5)], - ), - ACTOR, - logging_service, - ], + "pre_services": ServiceGroup( + before_handler=[time_measure_before_handler], + after_handler=[time_measure_after_handler], + components=[heavy_service for _ in range(0, 5)], + ), + "post_services": logging_service, } # %% diff --git a/tutorials/pipeline/7_extra_handlers_and_extensions.py b/tutorials/pipeline/7_extra_handlers_and_extensions.py index 619e2aaed..a89ce332d 100644 --- a/tutorials/pipeline/7_extra_handlers_and_extensions.py +++ b/tutorials/pipeline/7_extra_handlers_and_extensions.py @@ -25,9 +25,7 @@ GlobalExtraHandlerType, ExtraHandlerRuntimeInfo, ServiceRuntimeInfo, - ACTOR, ) - from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, @@ -61,7 +59,7 @@ * `global_extra_handler_type` (required) - A `GlobalExtraHandlerType` instance, indicates extra handler type to add. -* `extra_handler` (required) - The extra handler function itself. +* `extra_handler` (required) - The `ExtraHandlerFunction` itself. * `whitelist` - An optional list of paths, if it's not `None` the extra handlers will be applied to specified pipeline components only. @@ -124,10 +122,7 @@ async def long_service(_, __, info: ServiceRuntimeInfo): "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - [long_service for _ in range(0, 25)], - ACTOR, - ], + "pre_services": [long_service for _ in range(0, 25)], } # %% diff --git a/tutorials/script/core/1_basics.py b/tutorials/script/core/1_basics.py index ecde97725..49b39c41b 100644 --- a/tutorials/script/core/1_basics.py +++ b/tutorials/script/core/1_basics.py @@ -6,7 +6,7 @@ Here, basic usege of %mddoclink(api,pipeline.pipeline.pipeline,Pipeline) primitive is shown: its' creation with -%mddoclink(api,pipeline.pipeline.pipeline,Pipeline.from_script) +%mddoclink(api,pipeline.pipeline.pipeline,Pipeline) and execution. Additionally, function %mddoclink(api,utils.testing.common,check_happy_path) @@ -139,8 +139,8 @@ # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), ) diff --git a/tutorials/script/core/2_conditions.py b/tutorials/script/core/2_conditions.py index afe52762b..b355f5908 100644 --- a/tutorials/script/core/2_conditions.py +++ b/tutorials/script/core/2_conditions.py @@ -212,8 +212,8 @@ def internal_condition_function(ctx: Context, _: Pipeline) -> bool: ) # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), ) diff --git a/tutorials/script/core/3_responses.py b/tutorials/script/core/3_responses.py index 25fa067be..fa05ced13 100644 --- a/tutorials/script/core/3_responses.py +++ b/tutorials/script/core/3_responses.py @@ -189,8 +189,8 @@ def fallback_trace_response(ctx: Context, _: Pipeline) -> Message: random.seed(31415) # predestination of choice -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), ) diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 777e03652..0b7692498 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -295,8 +295,8 @@ def transition(_: Context, __: Pipeline) -> ConstLabel: ) # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("global_flow", "start_node"), fallback_label=("global_flow", "fallback_node"), ) diff --git a/tutorials/script/core/5_global_transitions.py b/tutorials/script/core/5_global_transitions.py index d6e3037a6..2b3c45005 100644 --- a/tutorials/script/core/5_global_transitions.py +++ b/tutorials/script/core/5_global_transitions.py @@ -196,8 +196,8 @@ ) # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("global_flow", "start_node"), fallback_label=("global_flow", "fallback_node"), ) diff --git a/tutorials/script/core/6_context_serialization.py b/tutorials/script/core/6_context_serialization.py index 759c715ef..fbbcc9bb1 100644 --- a/tutorials/script/core/6_context_serialization.py +++ b/tutorials/script/core/6_context_serialization.py @@ -77,8 +77,8 @@ def process_response(ctx: Context): # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("flow_start", "node_start"), post_services=[process_response], ) diff --git a/tutorials/script/core/7_pre_response_processing.py b/tutorials/script/core/7_pre_response_processing.py index 48a88f17a..0fe561d1e 100644 --- a/tutorials/script/core/7_pre_response_processing.py +++ b/tutorials/script/core/7_pre_response_processing.py @@ -117,8 +117,8 @@ def add_prefix_processing(ctx: Context, _: Pipeline): # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("root", "start"), fallback_label=("root", "fallback"), ) diff --git a/tutorials/script/core/8_misc.py b/tutorials/script/core/8_misc.py index 456fe232b..555f8eebc 100644 --- a/tutorials/script/core/8_misc.py +++ b/tutorials/script/core/8_misc.py @@ -142,8 +142,8 @@ def custom_response(ctx: Context, _: Pipeline) -> Message: # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("root", "start"), fallback_label=("root", "fallback"), ) diff --git a/tutorials/script/core/9_pre_transitions_processing.py b/tutorials/script/core/9_pre_transitions_processing.py index 3d5ff101c..e99fffdb3 100644 --- a/tutorials/script/core/9_pre_transitions_processing.py +++ b/tutorials/script/core/9_pre_transitions_processing.py @@ -87,8 +87,8 @@ def prepend_previous_node_response(ctx: Context, _: Pipeline): # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("root", "start"), fallback_label=("root", "fallback"), ) diff --git a/tutorials/script/responses/1_basics.py b/tutorials/script/responses/1_basics.py index feeb6e8ca..4202af96c 100644 --- a/tutorials/script/responses/1_basics.py +++ b/tutorials/script/responses/1_basics.py @@ -87,8 +87,8 @@ class CallbackRequest(NamedTuple): # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), ) diff --git a/tutorials/script/responses/2_media.py b/tutorials/script/responses/2_media.py index 16838d7dd..ed5f9c288 100644 --- a/tutorials/script/responses/2_media.py +++ b/tutorials/script/responses/2_media.py @@ -116,8 +116,8 @@ # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("root", "start"), fallback_label=("root", "fallback"), ) diff --git a/tutorials/script/responses/3_multi_message.py b/tutorials/script/responses/3_multi_message.py index d6504c260..1f24a7be8 100644 --- a/tutorials/script/responses/3_multi_message.py +++ b/tutorials/script/responses/3_multi_message.py @@ -144,8 +144,8 @@ # %% -pipeline = Pipeline.from_script( - toy_script, +pipeline = Pipeline( + script=toy_script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), ) diff --git a/tutorials/slots/1_basic_example.py b/tutorials/slots/1_basic_example.py index f65320c20..1c528de52 100644 --- a/tutorials/slots/1_basic_example.py +++ b/tutorials/slots/1_basic_example.py @@ -217,8 +217,8 @@ ] # %% -pipeline = Pipeline.from_script( - script, +pipeline = Pipeline( + script=script, start_label=("root", "start"), fallback_label=("root", "fallback"), slots=SLOTS, diff --git a/tutorials/stats/1_extractor_functions.py b/tutorials/stats/1_extractor_functions.py index 1597df5ea..a68d3f3fa 100644 --- a/tutorials/stats/1_extractor_functions.py +++ b/tutorials/stats/1_extractor_functions.py @@ -46,18 +46,16 @@ # %% import asyncio -from chatsky.script import Context from chatsky.pipeline import ( Pipeline, - ACTOR, - Service, ExtraHandlerRuntimeInfo, + GlobalExtraHandlerType, to_service, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT, HAPPY_PATH +from chatsky.script import Context from chatsky.stats import OtelInstrumentor, default_extractors from chatsky.utils.testing import is_interactive_mode, check_happy_path - +from chatsky.utils.testing.toy_script import TOY_SCRIPT, HAPPY_PATH # %% [markdown] """ @@ -118,22 +116,18 @@ async def heavy_service(ctx: Context): # %% -pipeline = Pipeline.from_dict( +pipeline = Pipeline.model_validate( { "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - heavy_service, - Service( - handler=ACTOR, - after_handler=[default_extractors.get_current_label], - ), - ], + "pre_services": heavy_service, } ) - +pipeline.actor.add_extra_handler( + GlobalExtraHandlerType.BEFORE, default_extractors.get_current_label +) if __name__ == "__main__": check_happy_path(pipeline, HAPPY_PATH) if is_interactive_mode(): diff --git a/tutorials/stats/2_pipeline_integration.py b/tutorials/stats/2_pipeline_integration.py index 0877aa6c0..928650f36 100644 --- a/tutorials/stats/2_pipeline_integration.py +++ b/tutorials/stats/2_pipeline_integration.py @@ -29,24 +29,22 @@ # %% import asyncio -from chatsky.script import Context from chatsky.pipeline import ( Pipeline, - ACTOR, - Service, ExtraHandlerRuntimeInfo, ServiceGroup, GlobalExtraHandlerType, ) -from chatsky.utils.testing.toy_script import TOY_SCRIPT, HAPPY_PATH +from chatsky.script import Context +from chatsky.stats import OTLPLogExporter, OTLPSpanExporter from chatsky.stats import ( OtelInstrumentor, set_logger_destination, set_tracer_destination, ) -from chatsky.stats import OTLPLogExporter, OTLPSpanExporter from chatsky.stats import default_extractors from chatsky.utils.testing import is_interactive_mode, check_happy_path +from chatsky.utils.testing.toy_script import TOY_SCRIPT, HAPPY_PATH # %% set_logger_destination(OTLPLogExporter("grpc://localhost:4317", insecure=True)) @@ -95,37 +93,39 @@ async def heavy_service(ctx: Context): """ # %% -pipeline = Pipeline.from_dict( +pipeline = Pipeline.model_validate( { "script": TOY_SCRIPT, "start_label": ("greeting_flow", "start_node"), "fallback_label": ("greeting_flow", "fallback_node"), - "components": [ - ServiceGroup( - before_handler=[default_extractors.get_timing_before], - after_handler=[ - get_service_state, - default_extractors.get_timing_after, - ], - components=[ - {"handler": heavy_service}, - {"handler": heavy_service}, - ], - ), - Service( - handler=ACTOR, - before_handler=[ - default_extractors.get_timing_before, - ], - after_handler=[ - get_service_state, - default_extractors.get_current_label, - default_extractors.get_timing_after, - ], - ), - ], + "pre_services": ServiceGroup( + before_handler=[default_extractors.get_timing_before], + after_handler=[ + get_service_state, + default_extractors.get_timing_after, + ], + components=[ + {"handler": heavy_service}, + {"handler": heavy_service}, + ], + ), } ) +# These are Extra Handlers for Actor. +pipeline.actor.add_extra_handler( + GlobalExtraHandlerType.BEFORE, default_extractors.get_timing_before +) +pipeline.actor.add_extra_handler( + GlobalExtraHandlerType.AFTER, get_service_state +) +pipeline.actor.add_extra_handler( + GlobalExtraHandlerType.AFTER, default_extractors.get_current_label +) +pipeline.actor.add_extra_handler( + GlobalExtraHandlerType.AFTER, default_extractors.get_timing_after +) + +# These are global Extra Handlers for Pipeline. pipeline.add_global_handler( GlobalExtraHandlerType.BEFORE_ALL, default_extractors.get_timing_before ) diff --git a/tutorials/utils/1_cache.py b/tutorials/utils/1_cache.py index 1f1dd7ec9..5c3b6980b 100644 --- a/tutorials/utils/1_cache.py +++ b/tutorials/utils/1_cache.py @@ -65,7 +65,7 @@ def response(_: Context, __: Pipeline) -> Message: (Message(), "5-6-5-6"), ) -pipeline = Pipeline.from_script(toy_script, start_label=("flow", "node1")) +pipeline = Pipeline(script=toy_script, start_label=("flow", "node1")) # %% diff --git a/tutorials/utils/2_lru_cache.py b/tutorials/utils/2_lru_cache.py index 0af5d27f2..241a0a2e2 100644 --- a/tutorials/utils/2_lru_cache.py +++ b/tutorials/utils/2_lru_cache.py @@ -64,7 +64,7 @@ def response(_: Context, __: Pipeline) -> Message: (Message(), "9-10-11-10-12"), ) -pipeline = Pipeline.from_script(toy_script, start_label=("flow", "node1")) +pipeline = Pipeline(script=toy_script, start_label=("flow", "node1")) # %% if __name__ == "__main__": diff --git a/utils/stats/sample_data_provider.py b/utils/stats/sample_data_provider.py index a880f5d9e..f24cfaf3c 100644 --- a/utils/stats/sample_data_provider.py +++ b/utils/stats/sample_data_provider.py @@ -12,7 +12,7 @@ import asyncio from tqdm import tqdm from chatsky.script import Context, Message -from chatsky.pipeline import Pipeline, Service, ACTOR, ExtraHandlerRuntimeInfo +from chatsky.pipeline import Pipeline, Service, ExtraHandlerRuntimeInfo, GlobalExtraHandlerType from chatsky.stats import ( default_extractors, OtelInstrumentor, @@ -52,30 +52,23 @@ async def get_confidence(ctx: Context, _, info: ExtraHandlerRuntimeInfo): # %% -pipeline = Pipeline.from_dict( +pipeline = Pipeline.model_validate( { "script": MULTIFLOW_SCRIPT, "start_label": ("root", "start"), "fallback_label": ("root", "fallback"), - "components": [ - Service(slot_processor_1, after_handler=[get_slots]), - Service(slot_processor_2, after_handler=[get_slots]), - Service( - handler=ACTOR, - before_handler=[ - default_extractors.get_timing_before, - ], - after_handler=[ - default_extractors.get_timing_after, - default_extractors.get_current_label, - default_extractors.get_last_request, - default_extractors.get_last_response, - ], - ), - Service(confidence_processor, after_handler=[get_confidence]), + "pre_services": [ + Service(handler=slot_processor_1, after_handler=[get_slots]), + Service(handler=slot_processor_2, after_handler=[get_slots]), ], + "post_services": Service(handler=confidence_processor, after_handler=[get_confidence]), } ) +pipeline.actor.add_extra_handler(GlobalExtraHandlerType.BEFORE, default_extractors.get_timing_before) +pipeline.actor.add_extra_handler(GlobalExtraHandlerType.AFTER, default_extractors.get_timing_after) +pipeline.actor.add_extra_handler(GlobalExtraHandlerType.AFTER, default_extractors.get_current_label) +pipeline.actor.add_extra_handler(GlobalExtraHandlerType.AFTER, default_extractors.get_last_request) +pipeline.actor.add_extra_handler(GlobalExtraHandlerType.AFTER, default_extractors.get_last_response) # %% From e61b89ad12dc8c43933d9b844bff360621c4094b Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 23 Aug 2024 14:01:38 +0300 Subject: [PATCH 04/11] update release checklist --- .github/process_github_events.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/process_github_events.py b/.github/process_github_events.py index 3f4331aeb..953028c2e 100644 --- a/.github/process_github_events.py +++ b/.github/process_github_events.py @@ -50,6 +50,9 @@ def post_comment_on_pr(comment: str, pr_number: int): - [ ] Change PR merge option - [ ] Update template repo - [ ] Search for objects to be deprecated +- [ ] Test parts not covered with pytest: + - [ ] web_api tutorials + - [ ] Test integrations with external services (telegram; stats) """ From 01dcb1b647bfe57fbfae2ed7260a5de2310f160b Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 2 Sep 2024 21:05:44 +0300 Subject: [PATCH 05/11] Feat/core rework (#381) # Description ## Breaking Changes - All conditions (standard and slot) moved to `chatsky.conditions` - All labels (standard) moved to `chatsky.destinations` - All processing functions (slot) moved to `chatsky.processing` - All responses (standard and slot) moved to `chatsky.responses` - All conditions, destinations, processing functions, responses are now upper camel case - `script.core` module moved to `chatsky.core` - `Pipeline` moved to `chatsky.core` - `pipeline.service` submodule moved to `chatsky.core.service` - Added class `Transition`. `TRANSITIONS` is now a list of such objects instead of a dict. Destination, condition and priority are all fields of this class. Priority is now separate from node label - Removed `cnd.true` and `cnd.false`. Condition field of `Transition` now accepts `True` and `False` literals. If condition field of `Transition` is not set, it defaults to `True`. - Removed `lbl.repeat` renamed to `dst.Current`. Added `dst.FromHistory` to get labels from history past `dst.Previous`. - Custom script functions now have to be subclassed from `BaseScriptFunction`. e.g. custom response function now have to be subclassed from `BaseResponse`. - All keywords from `chatsky.script.core.keywords` moved to `chatsky.core.script` - `PRE_TRANSITIONS_PROCESSING` renamed to `PRE_TRANSITION` - `PRE_RESPONSE_PROCESSING` renamed to `PRE_RESPONSE` - Removed `chatsky.utils.turn_caching` - Turn id `0` is now reserved for start label. Actual turns start at id 1. Context has method `init` to init from a start label ## Features - `RESPONSE` can now be a string. It will be converted to `Message(text=)` automatically - Custom functions do validation (e.g. response function can now return a string and it will be cast to `Message`) - Current instance of `Pipeline` can now be accessed via `Context.pipeline` - Added `NodeLabel` class to replace tuple node labels - The order of global/local transition inheritance for `ctx.current_node` is now reversed (`[*node, *local, *global]` instead of `[*global, *local, *node]`) to reflect the inheritance priority order (node>local>global) - Added method `Script.get_inherited_node` to get a node that inherits global and local properties. `ctx.current_node` is obtained via this method - Renamed response_comparer of `check_happy_path` to response_comparator, removed default comparators, removed context from signature; messages in `happy_path` can now be any of `Message`, `dict`, `str`; `printout_enable` flag renamed to `printout` and made `False` by default. ## Fixes - Slot template filling now works for numerical slot names (e.g. `slot_name="0"`) - Slot Groups can now be initialized from a dictionary - Exceptions in user functions are now properly handled: - response errors result in an empty message - processing errors are ignored - transition errors mark that specific transition as failed ## Documentation - Some improvements to basic conceptions user guide and global tutorial - Added a tip on getting previous nodes to pre transition tutorial - Removed `responses.1_basics` tutorial - Slight improvements to web api interface tutorials ## Devel - Added `log_event_catcher` fixture to help test captured logs - Added `TYPE_CHECKING` exclude from coverage; now only certain exception raises are excluded from coverage - Removed `run_interactive_mode`. Just use `pipeline.run()`. - Removed Annotated values with pickle serializer/validators. Better to use field_validators/field_serializers to preserve model schema - Added `InitTypes` for various BaseModels to indicate types that can be validated into that model - `Actor` now doesn't have any parameters - `normalization` utils replaced with pydantic validators - An instance of `Script` can now be validated from a script dict (without the `script` field) - Removed `Context.cast`. Use `model_validate` and `model_validate_json` instead. --- README.md | 58 ++- chatsky/__init__.py | 38 +- chatsky/__rebuild_pydantic_models__.py | 10 +- chatsky/conditions/__init__.py | 12 + chatsky/conditions/slots.py | 38 ++ chatsky/conditions/standard.py | 230 ++++++++++ chatsky/context_storages/database.py | 2 +- chatsky/context_storages/json.py | 15 +- chatsky/context_storages/mongo.py | 6 +- chatsky/context_storages/pickle.py | 4 +- chatsky/context_storages/redis.py | 6 +- chatsky/context_storages/shelve.py | 2 +- chatsky/context_storages/sql.py | 6 +- chatsky/context_storages/ydb.py | 6 +- chatsky/core/__init__.py | 13 + chatsky/core/context.py | 229 ++++++++++ chatsky/{script => }/core/message.py | 100 ++++- chatsky/core/node_label.py | 129 ++++++ chatsky/core/pipeline.py | 346 +++++++++++++++ chatsky/core/script.py | 173 ++++++++ chatsky/core/script_function.py | 251 +++++++++++ .../{pipeline => core/service}/__init__.py | 27 +- chatsky/core/service/actor.py | 134 ++++++ .../pipeline => core/service}/component.py | 54 +-- .../{pipeline => core/service}/conditions.py | 12 +- chatsky/{pipeline => core}/service/extra.py | 26 +- chatsky/{pipeline => core}/service/group.py | 36 +- chatsky/{pipeline => core}/service/service.py | 30 +- chatsky/{pipeline => core/service}/types.py | 8 +- chatsky/core/transition.py | 102 +++++ chatsky/{pipeline/pipeline => core}/utils.py | 17 +- chatsky/destinations/__init__.py | 1 + chatsky/destinations/standard.py | 143 ++++++ chatsky/messengers/common/interface.py | 14 +- chatsky/messengers/common/types.py | 4 +- chatsky/messengers/console.py | 12 +- chatsky/messengers/telegram/abstract.py | 4 +- chatsky/messengers/telegram/interface.py | 2 +- chatsky/pipeline/pipeline/__init__.py | 1 - chatsky/pipeline/pipeline/actor.py | 411 ------------------ chatsky/pipeline/pipeline/pipeline.py | 286 ------------ chatsky/pipeline/service/__init__.py | 1 - chatsky/processing/__init__.py | 2 + chatsky/processing/slots.py | 95 ++++ chatsky/processing/standard.py | 41 ++ chatsky/responses/__init__.py | 2 + chatsky/responses/slots.py | 61 +++ chatsky/responses/standard.py | 26 ++ chatsky/script/__init__.py | 26 -- chatsky/script/conditions/__init__.py | 18 - chatsky/script/conditions/std_conditions.py | 268 ------------ chatsky/script/core/__init__.py | 1 - chatsky/script/core/context.py | 283 ------------ chatsky/script/core/keywords.py | 101 ----- chatsky/script/core/normalization.py | 110 ----- chatsky/script/core/script.py | 267 ------------ chatsky/script/core/types.py | 113 ----- chatsky/script/extras/__init__.py | 1 - chatsky/script/extras/conditions/__init__.py | 1 - chatsky/script/extras/slots/__init__.py | 1 - chatsky/script/labels/__init__.py | 3 - chatsky/script/labels/std_labels.py | 183 -------- chatsky/script/responses/__init__.py | 3 - chatsky/script/responses/std_responses.py | 30 -- chatsky/slots/__init__.py | 6 - chatsky/slots/conditions.py | 32 -- chatsky/slots/processing.py | 98 ----- chatsky/slots/response.py | 34 -- chatsky/slots/slots.py | 106 +++-- chatsky/stats/default_extractors.py | 16 +- chatsky/stats/instrumentor.py | 4 +- chatsky/stats/utils.py | 2 +- chatsky/utils/db_benchmark/basic_config.py | 2 +- chatsky/utils/db_benchmark/benchmark.py | 2 +- chatsky/utils/devel/__init__.py | 6 +- chatsky/utils/devel/json_serialization.py | 41 +- chatsky/utils/parser/__init__.py | 1 - chatsky/utils/testing/__init__.py | 3 +- chatsky/utils/testing/common.py | 81 ++-- chatsky/utils/testing/response_comparers.py | 21 - chatsky/utils/testing/toy_script.py | 158 +++---- chatsky/utils/turn_caching/__init__.py | 3 - .../turn_caching/singleton_turn_caching.py | 50 --- chatsky/utils/viewer/__init__.py | 1 - docs/source/conf.py | 11 +- docs/source/get_started.rst | 2 +- docs/source/tutorials.rst | 2 +- docs/source/user_guides.rst | 2 +- docs/source/user_guides/basic_conceptions.rst | 159 ++++--- docs/source/user_guides/context_guide.rst | 107 ++--- .../source/user_guides/optimization_guide.rst | 5 +- docs/source/user_guides/slot_extraction.rst | 33 +- pyproject.toml | 7 +- tests/conftest.py | 26 ++ tests/context_storages/conftest.py | 2 +- tests/context_storages/test_dbs.py | 6 +- tests/{script => core}/__init__.py | 0 tests/core/conftest.py | 40 ++ tests/core/test_actor.py | 210 +++++++++ tests/core/test_conditions.py | 138 ++++++ tests/core/test_context.py | 147 +++++++ tests/core/test_destinations.py | 96 ++++ tests/{script => }/core/test_message.py | 5 +- tests/core/test_processing.py | 24 + tests/core/test_responses.py | 25 ++ tests/core/test_script.py | 84 ++++ tests/core/test_script_function.py | 142 ++++++ tests/core/test_transition.py | 61 +++ tests/messengers/telegram/test_tutorials.py | 2 +- tests/messengers/telegram/utils.py | 3 +- tests/pipeline/test_messenger_interface.py | 24 +- tests/pipeline/test_parallel_processing.py | 43 -- tests/pipeline/test_pipeline.py | 16 - tests/pipeline/test_update_ctx_misc.py | 15 +- tests/pipeline/test_validation.py | 51 +-- tests/script/conditions/__init__.py | 0 tests/script/conditions/test_conditions.py | 64 --- tests/script/core/__init__.py | 0 tests/script/core/test_actor.py | 200 --------- tests/script/core/test_context.py | 59 --- tests/script/core/test_normalization.py | 128 ------ tests/script/core/test_script.py | 122 ------ tests/script/core/test_validation.py | 215 --------- tests/script/labels/__init__.py | 0 tests/script/labels/test_labels.py | 44 -- tests/script/responses/__init__.py | 0 tests/script/responses/test_responses.py | 11 - tests/slots/conftest.py | 11 +- tests/slots/test_slot_functions.py | 147 +++++++ tests/slots/test_slot_manager.py | 411 +++++++++++------- tests/slots/test_slot_types.py | 43 +- tests/slots/test_tutorials.py | 20 - tests/stats/test_defaults.py | 30 +- tests/tutorials/test_utils.py | 7 +- tests/utils/test_benchmark.py | 2 +- tests/utils/test_serialization.py | 34 +- tutorials/context_storages/1_basics.py | 9 +- tutorials/context_storages/2_postgresql.py | 7 +- tutorials/context_storages/3_mongodb.py | 7 +- tutorials/context_storages/4_redis.py | 7 +- tutorials/context_storages/5_mysql.py | 7 +- tutorials/context_storages/6_sqlite.py | 7 +- .../context_storages/7_yandex_database.py | 7 +- tutorials/messengers/telegram/1_basic.py | 26 +- .../messengers/telegram/2_attachments.py | 69 +-- tutorials/messengers/telegram/3_advanced.py | 108 +++-- .../messengers/web_api_interface/1_fastapi.py | 16 +- .../web_api_interface/2_websocket_chat.py | 20 +- .../3_load_testing_with_locust.py | 21 +- .../web_api_interface/4_streamlit_chat.py | 4 +- tutorials/pipeline/1_basics.py | 21 +- .../pipeline/2_pre_and_post_processors.py | 8 +- .../3_pipeline_dict_with_services_basic.py | 12 +- .../3_pipeline_dict_with_services_full.py | 12 +- .../pipeline/4_groups_and_conditions_basic.py | 17 +- .../pipeline/4_groups_and_conditions_full.py | 21 +- ..._asynchronous_groups_and_services_basic.py | 9 +- ...5_asynchronous_groups_and_services_full.py | 9 +- tutorials/pipeline/6_extra_handlers_basic.py | 14 +- tutorials/pipeline/6_extra_handlers_full.py | 10 +- .../7_extra_handlers_and_extensions.py | 11 +- tutorials/script/core/1_basics.py | 90 ++-- tutorials/script/core/2_conditions.py | 219 +++++----- tutorials/script/core/3_responses.py | 202 +++++---- tutorials/script/core/4_transitions.py | 375 ++++++++-------- tutorials/script/core/5_global_local.py | 254 +++++++++++ tutorials/script/core/5_global_transitions.py | 208 --------- .../script/core/6_context_serialization.py | 36 +- .../script/core/7_pre_response_processing.py | 139 +++--- tutorials/script/core/8_misc.py | 115 ++--- .../core/9_pre_transition_processing.py | 139 ++++++ .../core/9_pre_transitions_processing.py | 99 ----- tutorials/script/responses/1_basics.py | 106 ----- tutorials/script/responses/1_media.py | 112 +++++ tutorials/script/responses/2_media.py | 128 ------ tutorials/script/responses/2_multi_message.py | 156 +++++++ tutorials/script/responses/3_multi_message.py | 156 ------- tutorials/slots/1_basic_example.py | 185 ++++---- tutorials/stats/1_extractor_functions.py | 7 +- tutorials/stats/2_pipeline_integration.py | 7 +- tutorials/utils/1_cache.py | 75 ---- tutorials/utils/2_lru_cache.py | 73 ---- utils/stats/sample_data_provider.py | 18 +- .../telegram_tutorial_data.py | 2 +- 184 files changed, 5831 insertions(+), 5867 deletions(-) create mode 100644 chatsky/conditions/__init__.py create mode 100644 chatsky/conditions/slots.py create mode 100644 chatsky/conditions/standard.py create mode 100644 chatsky/core/__init__.py create mode 100644 chatsky/core/context.py rename chatsky/{script => }/core/message.py (74%) create mode 100644 chatsky/core/node_label.py create mode 100644 chatsky/core/pipeline.py create mode 100644 chatsky/core/script.py create mode 100644 chatsky/core/script_function.py rename chatsky/{pipeline => core/service}/__init__.py (55%) create mode 100644 chatsky/core/service/actor.py rename chatsky/{pipeline/pipeline => core/service}/component.py (81%) rename chatsky/{pipeline => core/service}/conditions.py (88%) rename chatsky/{pipeline => core}/service/extra.py (90%) rename chatsky/{pipeline => core}/service/group.py (87%) rename chatsky/{pipeline => core}/service/service.py (87%) rename chatsky/{pipeline => core/service}/types.py (95%) create mode 100644 chatsky/core/transition.py rename chatsky/{pipeline/pipeline => core}/utils.py (73%) create mode 100644 chatsky/destinations/__init__.py create mode 100644 chatsky/destinations/standard.py delete mode 100644 chatsky/pipeline/pipeline/__init__.py delete mode 100644 chatsky/pipeline/pipeline/actor.py delete mode 100644 chatsky/pipeline/pipeline/pipeline.py delete mode 100644 chatsky/pipeline/service/__init__.py create mode 100644 chatsky/processing/__init__.py create mode 100644 chatsky/processing/slots.py create mode 100644 chatsky/processing/standard.py create mode 100644 chatsky/responses/__init__.py create mode 100644 chatsky/responses/slots.py create mode 100644 chatsky/responses/standard.py delete mode 100644 chatsky/script/__init__.py delete mode 100644 chatsky/script/conditions/__init__.py delete mode 100644 chatsky/script/conditions/std_conditions.py delete mode 100644 chatsky/script/core/__init__.py delete mode 100644 chatsky/script/core/context.py delete mode 100644 chatsky/script/core/keywords.py delete mode 100644 chatsky/script/core/normalization.py delete mode 100644 chatsky/script/core/script.py delete mode 100644 chatsky/script/core/types.py delete mode 100644 chatsky/script/extras/__init__.py delete mode 100644 chatsky/script/extras/conditions/__init__.py delete mode 100644 chatsky/script/extras/slots/__init__.py delete mode 100644 chatsky/script/labels/__init__.py delete mode 100644 chatsky/script/labels/std_labels.py delete mode 100644 chatsky/script/responses/__init__.py delete mode 100644 chatsky/script/responses/std_responses.py delete mode 100644 chatsky/slots/conditions.py delete mode 100644 chatsky/slots/processing.py delete mode 100644 chatsky/slots/response.py delete mode 100644 chatsky/utils/parser/__init__.py delete mode 100644 chatsky/utils/testing/response_comparers.py delete mode 100644 chatsky/utils/turn_caching/__init__.py delete mode 100644 chatsky/utils/turn_caching/singleton_turn_caching.py delete mode 100644 chatsky/utils/viewer/__init__.py rename tests/{script => core}/__init__.py (100%) create mode 100644 tests/core/conftest.py create mode 100644 tests/core/test_actor.py create mode 100644 tests/core/test_conditions.py create mode 100644 tests/core/test_context.py create mode 100644 tests/core/test_destinations.py rename tests/{script => }/core/test_message.py (96%) create mode 100644 tests/core/test_processing.py create mode 100644 tests/core/test_responses.py create mode 100644 tests/core/test_script.py create mode 100644 tests/core/test_script_function.py create mode 100644 tests/core/test_transition.py delete mode 100644 tests/pipeline/test_parallel_processing.py delete mode 100644 tests/pipeline/test_pipeline.py delete mode 100644 tests/script/conditions/__init__.py delete mode 100644 tests/script/conditions/test_conditions.py delete mode 100644 tests/script/core/__init__.py delete mode 100644 tests/script/core/test_actor.py delete mode 100644 tests/script/core/test_context.py delete mode 100644 tests/script/core/test_normalization.py delete mode 100644 tests/script/core/test_script.py delete mode 100644 tests/script/core/test_validation.py delete mode 100644 tests/script/labels/__init__.py delete mode 100644 tests/script/labels/test_labels.py delete mode 100644 tests/script/responses/__init__.py delete mode 100644 tests/script/responses/test_responses.py create mode 100644 tests/slots/test_slot_functions.py delete mode 100644 tests/slots/test_tutorials.py create mode 100644 tutorials/script/core/5_global_local.py delete mode 100644 tutorials/script/core/5_global_transitions.py create mode 100644 tutorials/script/core/9_pre_transition_processing.py delete mode 100644 tutorials/script/core/9_pre_transitions_processing.py delete mode 100644 tutorials/script/responses/1_basics.py create mode 100644 tutorials/script/responses/1_media.py delete mode 100644 tutorials/script/responses/2_media.py create mode 100644 tutorials/script/responses/2_multi_message.py delete mode 100644 tutorials/script/responses/3_multi_message.py delete mode 100644 tutorials/utils/1_cache.py delete mode 100644 tutorials/utils/2_lru_cache.py diff --git a/README.md b/README.md index 1358f08bb..754858db0 100644 --- a/README.md +++ b/README.md @@ -79,53 +79,47 @@ All the abstractions used in this example are thoroughly explained in the dedica [user guide](https://deeppavlov.github.io/chatsky/user_guides/basic_conceptions.html). ```python -from chatsky.script import GLOBAL, TRANSITIONS, RESPONSE, Message -from chatsky.pipeline import Pipeline -import chatsky.script.conditions.std_conditions as cnd +from chatsky import ( + GLOBAL, + TRANSITIONS, + RESPONSE, + Pipeline, + conditions as cnd, + Transition as Tr, +) # create a dialog script script = { GLOBAL: { - TRANSITIONS: { - ("flow", "node_hi"): cnd.exact_match("Hi"), - ("flow", "node_ok"): cnd.true() - } + TRANSITIONS: [ + Tr( + dst=("flow", "node_hi"), + cnd=cnd.ExactMatch("Hi"), + ), + Tr( + dst=("flow", "node_ok") + ) + ] }, "flow": { - "node_hi": {RESPONSE: Message("Hi!")}, - "node_ok": {RESPONSE: Message("OK")}, + "node_hi": {RESPONSE: "Hi!"}, + "node_ok": {RESPONSE: "OK"}, }, } -# init pipeline -pipeline = Pipeline.from_script(script, start_label=("flow", "node_hi")) +# initialize Pipeline (needed to run the script) +pipeline = Pipeline(script, start_label=("flow", "node_hi")) -def turn_handler(in_request: Message, pipeline: Pipeline) -> Message: - # Pass user request into pipeline and get dialog context (message history) - # The pipeline will automatically choose the correct response using script - ctx = pipeline(in_request, 0) - # Get last response from the context - out_response = ctx.last_response - return out_response - - -while True: - in_request = input("Your message: ") - out_response = turn_handler(Message(in_request), pipeline) - print("Response: ", out_response.text) +pipeline.run() ``` When you run this code, you get similar output: ``` -Your message: hi -Response: OK -Your message: Hi -Response: Hi! -Your message: ok -Response: OK -Your message: ok -Response: OK +request: hi +response: text='OK' +request: Hi +response: text='Hi!' ``` More advanced examples are available as a part of documentation: diff --git a/chatsky/__init__.py b/chatsky/__init__.py index 539647405..e1d7365f4 100644 --- a/chatsky/__init__.py +++ b/chatsky/__init__.py @@ -6,11 +6,41 @@ __version__ = version(__name__) -import nest_asyncio +import nest_asyncio as __nest_asyncio__ -nest_asyncio.apply() +__nest_asyncio__.apply() -from chatsky.pipeline import Pipeline -from chatsky.script import Context, Script +from chatsky.core import ( + GLOBAL, + LOCAL, + RESPONSE, + TRANSITIONS, + MISC, + PRE_RESPONSE, + PRE_TRANSITION, + BaseCondition, + AnyCondition, + BaseResponse, + AnyResponse, + BaseDestination, + AnyDestination, + BaseProcessing, + BasePriority, + AnyPriority, + Pipeline, + Context, + Message, + Transition, + Transition as Tr, + MessageInitTypes, + NodeLabel, + NodeLabelInitTypes, + AbsoluteNodeLabel, + AbsoluteNodeLabelInitTypes, +) +import chatsky.conditions as cnd +import chatsky.destinations as dst +import chatsky.responses as rsp +import chatsky.processing as proc import chatsky.__rebuild_pydantic_models__ diff --git a/chatsky/__rebuild_pydantic_models__.py b/chatsky/__rebuild_pydantic_models__.py index f648d6449..f2fc1de44 100644 --- a/chatsky/__rebuild_pydantic_models__.py +++ b/chatsky/__rebuild_pydantic_models__.py @@ -1,10 +1,14 @@ # flake8: noqa: F401 -from chatsky.pipeline import Pipeline -from chatsky.pipeline.types import ExtraHandlerRuntimeInfo, StartConditionCheckerFunction -from chatsky.script import Context, Script +from chatsky.core.service.types import ExtraHandlerRuntimeInfo, StartConditionCheckerFunction, ComponentExecutionState +from chatsky.core import Context, Script +from chatsky.core.script import Node +from chatsky.core.pipeline import Pipeline +from chatsky.slots.slots import SlotManager +from chatsky.core.context import FrameworkData Pipeline.model_rebuild() Script.model_rebuild() Context.model_rebuild() ExtraHandlerRuntimeInfo.model_rebuild() +FrameworkData.model_rebuild() diff --git a/chatsky/conditions/__init__.py b/chatsky/conditions/__init__.py new file mode 100644 index 000000000..b9a94b517 --- /dev/null +++ b/chatsky/conditions/__init__.py @@ -0,0 +1,12 @@ +from chatsky.conditions.standard import ( + ExactMatch, + HasText, + Regexp, + Any, + All, + Negation, + CheckLastLabels, + Not, + HasCallbackQuery, +) +from chatsky.conditions.slots import SlotsExtracted diff --git a/chatsky/conditions/slots.py b/chatsky/conditions/slots.py new file mode 100644 index 000000000..eaddd3140 --- /dev/null +++ b/chatsky/conditions/slots.py @@ -0,0 +1,38 @@ +""" +Slot Conditions +--------------------------- +Provides slot-related conditions. +""" + +from __future__ import annotations +from typing import Literal, List + +from chatsky.core import Context, BaseCondition +from chatsky.slots.slots import SlotName + + +class SlotsExtracted(BaseCondition): + """ + Check if :py:attr:`.slots` are extracted. + + :param mode: Whether to check if all slots are extracted or any slot is extracted. + """ + + slots: List[SlotName] + """ + Names of the slots that need to be checked. + """ + mode: Literal["any", "all"] = "all" + """ + Whether to check if all slots are extracted or any slot is extracted. + """ + + def __init__(self, *slots: SlotName, mode: Literal["any", "all"] = "all"): + super().__init__(slots=slots, mode=mode) + + async def call(self, ctx: Context) -> bool: + manager = ctx.framework_data.slot_manager + if self.mode == "all": + return all(manager.is_slot_extracted(slot) for slot in self.slots) + elif self.mode == "any": + return any(manager.is_slot_extracted(slot) for slot in self.slots) diff --git a/chatsky/conditions/standard.py b/chatsky/conditions/standard.py new file mode 100644 index 000000000..cf1a45013 --- /dev/null +++ b/chatsky/conditions/standard.py @@ -0,0 +1,230 @@ +""" +Standard Conditions +------------------- +This module provides basic conditions. + +- :py:class:`.Any`, :py:class:`.All` and :py:class:`.Negation` are meta-conditions. +- :py:class:`.HasText`, :py:class:`.Regexp`, :py:class:`.HasCallbackQuery` are last-request-based conditions. +- :py:class:`.CheckLastLabels` is a label-based condition. +""" + +import asyncio +from typing import Pattern, Union, List, Optional +import logging +import re +from functools import cached_property + +from pydantic import Field, computed_field + +from chatsky.core import BaseCondition, Context +from chatsky.core.message import Message, MessageInitTypes, CallbackQuery +from chatsky.core.node_label import AbsoluteNodeLabel, AbsoluteNodeLabelInitTypes + +logger = logging.getLogger(__name__) + + +class ExactMatch(BaseCondition): + """ + Check if :py:attr:`~.Context.last_request` matches :py:attr:`.match`. + + If :py:attr:`.skip_none`, will not compare ``None`` fields of :py:attr:`.match`. + """ + + match: Message + """ + Message to compare last request with. + + Is initialized according to :py:data:`~.MessageInitTypes`. + """ + skip_none: bool = True + """ + Whether fields set to ``None`` in :py:attr:`.match` should not be compared. + """ + + def __init__(self, match: MessageInitTypes, *, skip_none=True): + super().__init__(match=match, skip_none=skip_none) + + async def call(self, ctx: Context) -> bool: + request = ctx.last_request + for field in self.match.model_fields: + match_value = self.match.__getattribute__(field) + if self.skip_none and match_value is None: + continue + if field in request.model_fields.keys(): + if request.__getattribute__(field) != self.match.__getattribute__(field): + return False + else: + return False + return True + + +class HasText(BaseCondition): + """ + Check if the :py:attr:`~.Message.text` attribute of :py:attr:`~.Context.last_request` + contains :py:attr:`.text`. + """ + + text: str + """ + Text to search for in the last request. + """ + + def __init__(self, text): + super().__init__(text=text) + + async def call(self, ctx: Context) -> bool: + request = ctx.last_request + if request.text is None: + return False + return self.text in request.text + + +class Regexp(BaseCondition): + """ + Check if the :py:attr:`~.Message.text` attribute of :py:attr:`~.Context.last_request` + contains :py:attr:`.pattern`. + """ + + pattern: Union[str, Pattern] + """ + The `RegExp` pattern to search for in the last request. + """ + flags: Union[int, re.RegexFlag] = 0 + """ + Flags to pass to ``re.compile``. + """ + + def __init__(self, pattern, *, flags=0): + super().__init__(pattern=pattern, flags=flags) + + @computed_field + @cached_property + def re_object(self) -> Pattern: + """Compiled pattern.""" + return re.compile(self.pattern, self.flags) + + async def call(self, ctx: Context) -> bool: + request = ctx.last_request + if request.text is None: + return False + return bool(self.re_object.search(request.text)) + + +class Any(BaseCondition): + """ + Check if any condition from the :py:attr:`.conditions` list is True. + """ + + conditions: List[BaseCondition] + """ + List of conditions. + """ + + def __init__(self, *conditions): + super().__init__(conditions=list(conditions)) + + async def call(self, ctx: Context) -> bool: + return any(await asyncio.gather(*(cnd.is_true(ctx) for cnd in self.conditions))) + + +class All(BaseCondition): + """ + Check if all conditions from the :py:attr:`.conditions` list is True. + """ + + conditions: List[BaseCondition] + """ + List of conditions. + """ + + def __init__(self, *conditions): + super().__init__(conditions=list(conditions)) + + async def call(self, ctx: Context) -> bool: + return all(await asyncio.gather(*(cnd.is_true(ctx) for cnd in self.conditions))) + + +class Negation(BaseCondition): + """ + Return the negation of the result of :py:attr:`~.Negation.condition`. + """ + + condition: BaseCondition + """ + Condition to negate. + """ + + def __init__(self, condition): + super().__init__(condition=condition) + + async def call(self, ctx: Context) -> bool: + return not await self.condition.is_true(ctx) + + +Not = Negation +""" +:py:class:`.Not` is an alias for :py:class:`.Negation`. +""" + + +class CheckLastLabels(BaseCondition): + """ + Check if any label in the last :py:attr:`.last_n_indices` of :py:attr:`.Context.labels` is in + :py:attr:`.labels` or if its :py:attr:`~.AbsoluteNodeLabel.flow_name` is in :py:attr:`.flow_labels`. + """ + + flow_labels: List[str] = Field(default_factory=list) + """ + List of flow names to find in the last labels. + """ + labels: List[AbsoluteNodeLabel] = Field(default_factory=list) + """ + List of labels to find in the last labels. + + Is initialized according to :py:data:`~.AbsoluteNodeLabelInitTypes`. + """ + last_n_indices: int = Field(default=1, ge=1) + """ + Number of labels to check. + """ + + def __init__( + self, *, flow_labels=None, labels: Optional[List[AbsoluteNodeLabelInitTypes]] = None, last_n_indices=1 + ): + if flow_labels is None: + flow_labels = [] + if labels is None: + labels = [] + super().__init__(flow_labels=flow_labels, labels=labels, last_n_indices=last_n_indices) + + async def call(self, ctx: Context) -> bool: + labels = list(ctx.labels.values())[-self.last_n_indices :] # noqa: E203 + for label in labels: + if label.flow_name in self.flow_labels or label in self.labels: + return True + return False + + +class HasCallbackQuery(BaseCondition): + """ + Check if :py:attr:`~.Context.last_request` contains a :py:class:`.CallbackQuery` attachment + with :py:attr:`.CallbackQuery.query_string` matching :py:attr:`.HasCallbackQuery.query_string`. + """ + + query_string: str + """ + Query string to find in last request's attachments. + """ + + def __init__(self, query_string): + super().__init__(query_string=query_string) + + async def call(self, ctx: Context) -> bool: + last_request = ctx.last_request + if last_request.attachments is None: + return False + for attachment in last_request.attachments: + if isinstance(attachment, CallbackQuery): + if attachment.query_string == self.query_string: + return True + return False diff --git a/chatsky/context_storages/database.py b/chatsky/context_storages/database.py index 3d3a857d1..faa70caf4 100644 --- a/chatsky/context_storages/database.py +++ b/chatsky/context_storages/database.py @@ -16,7 +16,7 @@ from typing import Callable, Hashable, Optional from .protocol import PROTOCOLS -from ..script import Context +from chatsky.core import Context class DBContextStorage(ABC): diff --git a/chatsky/context_storages/json.py b/chatsky/context_storages/json.py index 9ecc44b63..21b84e36f 100644 --- a/chatsky/context_storages/json.py +++ b/chatsky/context_storages/json.py @@ -7,7 +7,7 @@ """ import asyncio -from typing import Hashable +from typing import Hashable, Dict try: import aiofiles @@ -17,19 +17,14 @@ except ImportError: json_available = False -from pydantic import BaseModel, model_validator +from pydantic import BaseModel from .database import DBContextStorage, threadsafe_method -from chatsky.script import Context +from chatsky.core import Context class SerializableStorage(BaseModel, extra="allow"): - @model_validator(mode="before") - @classmethod - def validate_any(cls, vals): - for key, value in vals.items(): - vals[key] = Context.cast(value) - return vals + __pydantic_extra__: Dict[str, Context] class JSONContextStorage(DBContextStorage): @@ -55,7 +50,7 @@ async def set_item_async(self, key: Hashable, value: Context): @threadsafe_method async def get_item_async(self, key: Hashable) -> Context: await self._load() - return Context.cast(self.storage.model_extra.__getitem__(str(key))) + return Context.model_validate(self.storage.model_extra.__getitem__(str(key))) @threadsafe_method async def del_item_async(self, key: Hashable): diff --git a/chatsky/context_storages/mongo.py b/chatsky/context_storages/mongo.py index 166045a12..3bc6d1956 100644 --- a/chatsky/context_storages/mongo.py +++ b/chatsky/context_storages/mongo.py @@ -26,7 +26,7 @@ import json -from chatsky.script import Context +from chatsky.core import Context from .database import DBContextStorage, threadsafe_method from .protocol import get_protocol_install_suggestion @@ -60,7 +60,7 @@ def _adjust_key(key: Hashable) -> Dict[str, ObjectId]: @threadsafe_method async def set_item_async(self, key: Hashable, value: Context): new_key = self._adjust_key(key) - value = value if isinstance(value, Context) else Context.cast(value) + value = Context.model_validate(value) document = json.loads(value.model_dump_json()) document.update(new_key) @@ -72,7 +72,7 @@ async def get_item_async(self, key: Hashable) -> Context: document = await self.collection.find_one(adjust_key) if document: document.pop("_id") - ctx = Context.cast(document) + ctx = Context.model_validate(document) return ctx raise KeyError diff --git a/chatsky/context_storages/pickle.py b/chatsky/context_storages/pickle.py index 9f72a22c3..eb1ddeb0c 100644 --- a/chatsky/context_storages/pickle.py +++ b/chatsky/context_storages/pickle.py @@ -24,7 +24,7 @@ pickle_available = False from .database import DBContextStorage, threadsafe_method -from chatsky.script import Context +from chatsky.core import Context class PickleContextStorage(DBContextStorage): @@ -50,7 +50,7 @@ async def set_item_async(self, key: Hashable, value: Context): @threadsafe_method async def get_item_async(self, key: Hashable) -> Context: await self._load() - return Context.cast(self.dict.__getitem__(str(key))) + return Context.model_validate(self.dict.__getitem__(str(key))) @threadsafe_method async def del_item_async(self, key: Hashable): diff --git a/chatsky/context_storages/redis.py b/chatsky/context_storages/redis.py index 7334097c7..e3165cacd 100644 --- a/chatsky/context_storages/redis.py +++ b/chatsky/context_storages/redis.py @@ -23,7 +23,7 @@ except ImportError: redis_available = False -from chatsky.script import Context +from chatsky.core import Context from .database import DBContextStorage, threadsafe_method from .protocol import get_protocol_install_suggestion @@ -49,7 +49,7 @@ async def contains_async(self, key: Hashable) -> bool: @threadsafe_method async def set_item_async(self, key: Hashable, value: Context): - value = value if isinstance(value, Context) else Context.cast(value) + value = Context.model_validate(value) await self._redis.set(str(key), value.model_dump_json()) @threadsafe_method @@ -57,7 +57,7 @@ async def get_item_async(self, key: Hashable) -> Context: result = await self._redis.get(str(key)) if result: result_dict = json.loads(result.decode("utf-8")) - return Context.cast(result_dict) + return Context.model_validate(result_dict) raise KeyError(f"No entry for key {key}.") @threadsafe_method diff --git a/chatsky/context_storages/shelve.py b/chatsky/context_storages/shelve.py index de2e97ea5..82fc5ca87 100644 --- a/chatsky/context_storages/shelve.py +++ b/chatsky/context_storages/shelve.py @@ -17,7 +17,7 @@ from shelve import DbfilenameShelf from typing import Hashable -from chatsky.script import Context +from chatsky.core import Context from .database import DBContextStorage diff --git a/chatsky/context_storages/sql.py b/chatsky/context_storages/sql.py index 677c1648d..4fafa9dc5 100644 --- a/chatsky/context_storages/sql.py +++ b/chatsky/context_storages/sql.py @@ -18,7 +18,7 @@ import json from typing import Hashable -from chatsky.script import Context +from chatsky.core import Context from .database import DBContextStorage, threadsafe_method from .protocol import get_protocol_install_suggestion @@ -115,7 +115,7 @@ def __init__(self, path: str, table_name: str = "contexts", custom_driver: bool @threadsafe_method async def set_item_async(self, key: Hashable, value: Context): - value = value if isinstance(value, Context) else Context.cast(value) + value = Context.model_validate(value) value = json.loads(value.model_dump_json()) insert_stmt = insert(self.table).values(id=str(key), context=value) @@ -132,7 +132,7 @@ async def get_item_async(self, key: Hashable) -> Context: result = await conn.execute(stmt) row = result.fetchone() if row: - return Context.cast(row[0]) + return Context.model_validate(row[0]) raise KeyError @threadsafe_method diff --git a/chatsky/context_storages/ydb.py b/chatsky/context_storages/ydb.py index ff50f5b7b..82a4465a6 100644 --- a/chatsky/context_storages/ydb.py +++ b/chatsky/context_storages/ydb.py @@ -16,7 +16,7 @@ from urllib.parse import urlsplit -from chatsky.script import Context +from chatsky.core import Context from .database import DBContextStorage from .protocol import get_protocol_install_suggestion @@ -51,7 +51,7 @@ def __init__(self, path: str, table_name: str = "contexts", timeout=5): self.driver, self.pool = asyncio.run(_init_drive(timeout, self.endpoint, self.database, self.table_name)) async def set_item_async(self, key: Hashable, value: Context): - value = value if isinstance(value, Context) else Context.cast(value) + value = Context.model_validate(value) async def callee(session): query = """ @@ -104,7 +104,7 @@ async def callee(session): commit_tx=True, ) if result_sets[0].rows: - return Context.cast(result_sets[0].rows[0].context) + return Context.model_validate_json(result_sets[0].rows[0].context) else: raise KeyError diff --git a/chatsky/core/__init__.py b/chatsky/core/__init__.py new file mode 100644 index 000000000..7f18e72a7 --- /dev/null +++ b/chatsky/core/__init__.py @@ -0,0 +1,13 @@ +""" +This module defines core feature of the Chatsky framework. +""" + +from chatsky.core.context import Context +from chatsky.core.message import Message, MessageInitTypes +from chatsky.core.pipeline import Pipeline +from chatsky.core.script import Node, Flow, Script +from chatsky.core.script_function import BaseCondition, BaseResponse, BaseDestination, BaseProcessing, BasePriority +from chatsky.core.script_function import AnyCondition, AnyResponse, AnyDestination, AnyPriority +from chatsky.core.transition import Transition +from chatsky.core.node_label import NodeLabel, NodeLabelInitTypes, AbsoluteNodeLabel, AbsoluteNodeLabelInitTypes +from chatsky.core.script import GLOBAL, LOCAL, RESPONSE, TRANSITIONS, MISC, PRE_RESPONSE, PRE_TRANSITION diff --git a/chatsky/core/context.py b/chatsky/core/context.py new file mode 100644 index 000000000..e46ae718c --- /dev/null +++ b/chatsky/core/context.py @@ -0,0 +1,229 @@ +""" +Context +------- +Context is a data structure that is used to store information about the current state of a conversation. + +It is used to keep track of the user's input, the current stage of the conversation, and any other +information that is relevant to the current context of a dialog. + +The Context data structure provides several key features to make working with data easier. +Developers can use the context to store any information that is relevant to the current conversation, +such as user data, session data, conversation history, e.t.c. +This allows developers to easily access and use this data throughout the conversation flow. + +Another important feature of the context is data serialization. +The context can be easily serialized to a format that can be stored or transmitted, such as JSON. +This allows developers to save the context data and resume the conversation later. +""" + +from __future__ import annotations +import logging +from uuid import UUID, uuid4 +from typing import Any, Optional, Union, Dict, TYPE_CHECKING + +from pydantic import BaseModel, Field + +from chatsky.core.message import Message, MessageInitTypes +from chatsky.slots.slots import SlotManager +from chatsky.core.node_label import AbsoluteNodeLabel, AbsoluteNodeLabelInitTypes + +if TYPE_CHECKING: + from chatsky.core.script import Node + from chatsky.core.pipeline import Pipeline + from chatsky.core.service.types import ComponentExecutionState + +logger = logging.getLogger(__name__) + + +def get_last_index(dictionary: dict) -> int: + """ + Obtain the last index from the `dictionary`. + + :param dictionary: Dictionary with unsorted keys. + :return: Last index from the `dictionary`. + :raises ValueError: If the dictionary is empty. + """ + if len(dictionary) == 0: + raise ValueError("Dictionary is empty.") + indices = list(dictionary) + return max(indices) + + +class ContextError(Exception): + """Raised when context methods are not used correctly.""" + + +class FrameworkData(BaseModel): + """ + Framework uses this to store data related to any of its modules. + """ + + service_states: Dict[str, ComponentExecutionState] = Field(default_factory=dict, exclude=True) + "Statuses of all the pipeline services. Cleared at the end of every turn." + current_node: Optional[Node] = Field(default=None, exclude=True) + """ + A copy of the current node provided by :py:meth:`~chatsky.core.script.Script.get_inherited_node`. + This node can be safely modified by Processing functions to alter current node fields. + """ + pipeline: Optional[Pipeline] = Field(default=None, exclude=True) + """ + Instance of the pipeline that manages this context. + Can be used to obtain run configuration such as script or fallback label. + """ + stats: Dict[str, Any] = Field(default_factory=dict) + "Enables complex stats collection across multiple turns." + slot_manager: SlotManager = Field(default_factory=SlotManager) + "Stores extracted slots." + + +class Context(BaseModel): + """ + A structure that is used to store data about the context of a dialog. + """ + + id: Union[UUID, int, str] = Field(default_factory=uuid4) + """ + ``id`` is the unique context identifier. By default, randomly generated using ``uuid4``. + ``id`` can be used to trace the user behavior, e.g while collecting the statistical data. + """ + labels: Dict[int, AbsoluteNodeLabel] = Field(default_factory=dict) + """ + ``labels`` stores the history of labels for all passed nodes. + + - key - ``id`` of the turn. + - value - ``label`` of this turn. + + Start label is stored at key ``0``. + IDs go up by ``1`` after that. + """ + requests: Dict[int, Message] = Field(default_factory=dict) + """ + ``requests`` stores the history of all requests received by the pipeline. + + - key - ``id`` of the turn. + - value - ``request`` of this turn. + + First request is stored at key ``1``. + IDs go up by ``1`` after that. + """ + responses: Dict[int, Message] = Field(default_factory=dict) + """ + ``responses`` stores the history of all responses produced by the pipeline. + + - key - ``id`` of the turn. + - value - ``response`` of this turn. + + First response is stored at key ``1``. + IDs go up by ``1`` after that. + """ + misc: Dict[str, Any] = Field(default_factory=dict) + """ + ``misc`` stores any custom data. The framework doesn't use this dictionary, + so storage of any data won't reflect on the work of the internal Chatsky functions. + + - key - Arbitrary data name. + - value - Arbitrary data. + """ + framework_data: FrameworkData = Field(default_factory=FrameworkData) + """ + This attribute is used for storing custom data required for pipeline execution. + It is meant to be used by the framework only. Accessing it may result in pipeline breakage. + """ + + @classmethod + def init(cls, start_label: AbsoluteNodeLabelInitTypes, id: Optional[Union[UUID, int, str]] = None): + """Initialize new context from ``start_label`` and, optionally, context ``id``.""" + init_kwargs = { + "labels": {0: AbsoluteNodeLabel.model_validate(start_label)}, + } + if id is None: + return cls(**init_kwargs) + else: + return cls(**init_kwargs, id=id) + + def add_request(self, request: MessageInitTypes): + """ + Add a new ``request`` to the context. + """ + request_message = Message.model_validate(request) + if len(self.requests) == 0: + self.requests[1] = request_message + else: + last_index = get_last_index(self.requests) + self.requests[last_index + 1] = request_message + + def add_response(self, response: MessageInitTypes): + """ + Add a new ``response`` to the context. + """ + response_message = Message.model_validate(response) + if len(self.responses) == 0: + self.responses[1] = response_message + else: + last_index = get_last_index(self.responses) + self.responses[last_index + 1] = response_message + + def add_label(self, label: AbsoluteNodeLabelInitTypes): + """ + Add a new :py:class:`~.AbsoluteNodeLabel` to the context. + + :raises ContextError: If :py:attr:`labels` is empty. + """ + label = AbsoluteNodeLabel.model_validate(label) + if len(self.labels) == 0: + raise ContextError("Labels are empty. Use `Context.init` to initialize context with labels.") + last_index = get_last_index(self.labels) + self.labels[last_index + 1] = label + + @property + def last_label(self) -> AbsoluteNodeLabel: + """ + Return the last :py:class:`~.AbsoluteNodeLabel` of + the :py:class:`~.Context`. + + :raises ContextError: If :py:attr:`labels` is empty. + """ + if len(self.labels) == 0: + raise ContextError("Labels are empty. Use `Context.init` to initialize context with labels.") + last_index = get_last_index(self.labels) + return self.labels[last_index] + + @property + def last_response(self) -> Optional[Message]: + """ + Return the last response of the current :py:class:`~.Context`. + Return ``None`` if no responses have been added yet. + """ + if len(self.responses) == 0: + return None + last_index = get_last_index(self.responses) + response = self.responses[last_index] + return response + + @property + def last_request(self) -> Message: + """ + Return the last request of the current :py:class:`~.Context`. + + :raises ContextError: If :py:attr:`responses` is empty. + """ + if len(self.requests) == 0: + raise ContextError("No requests have been added.") + last_index = get_last_index(self.requests) + return self.requests[last_index] + + @property + def pipeline(self) -> Pipeline: + """Return :py:attr:`.FrameworkData.pipeline`.""" + pipeline = self.framework_data.pipeline + if pipeline is None: + raise ContextError("Pipeline is not set.") + return pipeline + + @property + def current_node(self) -> Node: + """Return :py:attr:`.FrameworkData.current_node`.""" + node = self.framework_data.current_node + if node is None: + raise ContextError("Current node is not set.") + return node diff --git a/chatsky/script/core/message.py b/chatsky/core/message.py similarity index 74% rename from chatsky/script/core/message.py rename to chatsky/core/message.py index 79120598c..006939137 100644 --- a/chatsky/script/core/message.py +++ b/chatsky/core/message.py @@ -1,21 +1,29 @@ """ Message ------- -The :py:class:`.Message` class is a universal data model for representing a message that should be supported by -Chatsky. It only contains types and properties that are compatible with most messaging services. +The Message class is a universal data model for representing a message. + +It only contains types and properties that are compatible with most messaging services. """ -from typing import Literal, Optional, List, Union +from typing import Literal, Optional, List, Union, Dict, Any +from typing_extensions import TypeAlias, Annotated from pathlib import Path from urllib.request import urlopen import uuid import abc -from pydantic import Field, FilePath, HttpUrl, model_validator +from pydantic import Field, FilePath, HttpUrl, model_validator, field_validator, field_serializer from pydantic_core import Url from chatsky.messengers.common.interface import MessengerInterfaceWithAttachments -from chatsky.utils.devel import JSONSerializableDict, PickleEncodedValue, JSONSerializableExtras +from chatsky.utils.devel import ( + json_pickle_validator, + json_pickle_serializer, + pickle_serializer, + pickle_validator, + JSONSerializableExtras, +) class DataModel(JSONSerializableExtras): @@ -42,7 +50,7 @@ class CallbackQuery(Attachment): It has query string attribute, that represents the response data string. """ - query_string: Optional[str] + query_string: str chatsky_attachment_type: Literal["callback_query"] = "callback_query" @@ -278,13 +286,14 @@ class level variables to store message information. ] ] ] = None - annotations: Optional[JSONSerializableDict] = None - misc: Optional[JSONSerializableDict] = None - original_message: Optional[PickleEncodedValue] = None + annotations: Optional[Dict[str, Any]] = None + misc: Optional[Dict[str, Any]] = None + original_message: Optional[Any] = None - def __init__( + def __init__( # this allows initializing Message with string as positional argument self, text: Optional[str] = None, + *, attachments: Optional[ List[ Union[ @@ -305,11 +314,74 @@ def __init__( ] ] ] = None, - annotations: Optional[JSONSerializableDict] = None, - misc: Optional[JSONSerializableDict] = None, + annotations: Optional[Dict[str, Any]] = None, + misc: Optional[Dict[str, Any]] = None, + original_message: Optional[Any] = None, **kwargs, ): - super().__init__(text=text, attachments=attachments, annotations=annotations, misc=misc, **kwargs) + super().__init__( + text=text, + attachments=attachments, + annotations=annotations, + misc=misc, + original_message=original_message, + **kwargs, + ) + + @field_serializer("annotations", "misc", when_used="json") + def pickle_serialize_dicts(self, value): + """ + Serialize values that are not json-serializable via pickle. + Allows storing arbitrary data in misc/annotations when using context storages. + """ + if isinstance(value, dict): + return json_pickle_serializer(value) + return value + + @field_validator("annotations", "misc", mode="before") + @classmethod + def pickle_validate_dicts(cls, value): + """Restore values serialized with :py:meth:`pickle_serialize_dicts`.""" + if isinstance(value, dict): + return json_pickle_validator(value) + return value + + @field_serializer("original_message", when_used="json") + def pickle_serialize_original_message(self, value): + """ + Cast :py:attr:`original_message` to string via pickle. + Allows storing arbitrary data in this field when using context storages. + """ + if value is not None: + return pickle_serializer(value) + return value + + @field_validator("original_message", mode="before") + @classmethod + def pickle_validate_original_message(cls, value): + """ + Restore :py:attr:`original_message` after being processed with + :py:meth:`pickle_serialize_original_message`. + """ + if value is not None: + return pickle_validator(value) + return value - def __repr__(self) -> str: + def __str__(self) -> str: return " ".join([f"{key}='{value}'" for key, value in self.model_dump(exclude_none=True).items()]) + + @model_validator(mode="before") + @classmethod + def validate_from_str(cls, data): + """ + Allow instantiating this class from a single string which becomes :py:attr:`Message.text` + """ + if isinstance(data, str): + return {"text": data} + return data + + +MessageInitTypes: TypeAlias = Union[ + Message, Annotated[dict, "dict following the Message data model"], Annotated[str, "message text"] +] +"""Types that :py:class:`~.Message` can be validated from.""" diff --git a/chatsky/core/node_label.py b/chatsky/core/node_label.py new file mode 100644 index 000000000..1e032dcb1 --- /dev/null +++ b/chatsky/core/node_label.py @@ -0,0 +1,129 @@ +""" +Node Label +---------- +This module defines classes for addressing nodes. +""" + +from __future__ import annotations + +from typing import Optional, Union, Tuple, TYPE_CHECKING +from typing_extensions import TypeAlias, Annotated + +from pydantic import BaseModel, model_validator, ValidationInfo + +if TYPE_CHECKING: + from chatsky.core.context import Context + + +def _get_current_flow_name(ctx: Context) -> str: + """Get flow name of the current node from context.""" + current_node = ctx.last_label + return current_node.flow_name + + +class NodeLabel(BaseModel, frozen=True): + """ + A label for a node. (a way to address a specific node in the script) + + Can be relative if :py:attr:`flow_name` is ``None``: + such ``NodeLabel`` will reference a node with the name :py:attr:`node_name` + in the current flow. + """ + + flow_name: Optional[str] = None + """ + Name of the flow in the script. + Can be ``None`` in which case this is inherited from the :py:attr:`.Context.current_node`. + """ + node_name: str + """ + Name of the node in the flow. + """ + + @model_validator(mode="before") + @classmethod + def validate_from_str_or_tuple(cls, data, info: ValidationInfo): + """ + Allow instantiating of this class from: + + - A single string (node name). Also attempt to get the current flow name from context. + - A tuple of two strings (flow and node name). + """ + if isinstance(data, str): + flow_name = None + context = info.context + if isinstance(context, dict): + flow_name = _get_current_flow_name(context.get("ctx")) + return {"flow_name": flow_name, "node_name": data} + elif isinstance(data, tuple): + if len(data) == 2 and isinstance(data[0], str) and isinstance(data[1], str): + return {"flow_name": data[0], "node_name": data[1]} + else: + raise ValueError(f"Cannot validate NodeLabel from {data!r}: tuple should contain 2 strings.") + return data + + +NodeLabelInitTypes: TypeAlias = Union[ + NodeLabel, + Annotated[str, "node_name, flow name equal to current flow's name"], + Tuple[Annotated[str, "flow_name"], Annotated[str, "node_name"]], + Annotated[dict, "dict following the NodeLabel data model"], +] +"""Types that :py:class:`~.NodeLabel` can be validated from.""" + + +class AbsoluteNodeLabel(NodeLabel): + """ + A label for a node. (a way to address a specific node in the script) + """ + + flow_name: str + """ + Name of the flow in the script. + """ + node_name: str + """ + Name of the node in the flow. + """ + + @model_validator(mode="before") + @classmethod + def validate_from_node_label(cls, data, info: ValidationInfo): + """ + Allow instantiating of this class from :py:class:`NodeLabel`. + + Attempt to get the current flow name from context if :py:attr:`NodeLabel.flow_name` is empty. + """ + if isinstance(data, NodeLabel): + flow_name = data.flow_name + if flow_name is None: + context = info.context + if isinstance(context, dict): + flow_name = _get_current_flow_name(context.get("ctx")) + return {"flow_name": flow_name, "node_name": data.node_name} + return data + + @model_validator(mode="after") + def check_node_exists(self, info: ValidationInfo): + """ + Validate node exists in the script. + """ + context = info.context + if isinstance(context, dict): + ctx: Context = info.context.get("ctx") + if ctx is not None: + script = ctx.pipeline.script + + node = script.get_node(self) + if node is None: + raise ValueError(f"Cannot find node {self!r} in script.") + return self + + +AbsoluteNodeLabelInitTypes: TypeAlias = Union[ + AbsoluteNodeLabel, + NodeLabel, + Tuple[Annotated[str, "flow_name"], Annotated[str, "node_name"]], + Annotated[dict, "dict following the AbsoluteNodeLabel data model"], +] +"""Types that :py:class:`~.AbsoluteNodeLabel` can be validated from.""" diff --git a/chatsky/core/pipeline.py b/chatsky/core/pipeline.py new file mode 100644 index 000000000..8c51692bf --- /dev/null +++ b/chatsky/core/pipeline.py @@ -0,0 +1,346 @@ +""" +Pipeline +-------- +Pipeline is the main element of the Chatsky framework. + +Pipeline is responsible for managing and executing the various components +(:py:class:`~chatsky.core.service.component.PipelineComponent`) +including :py:class:`.Actor`. +""" + +import asyncio +import logging +from functools import cached_property +from typing import Union, List, Dict, Optional, Hashable +from pydantic import BaseModel, Field, model_validator, computed_field + +from chatsky.context_storages import DBContextStorage +from chatsky.core.script import Script +from chatsky.core.context import Context +from chatsky.core.message import Message + +from chatsky.messengers.console import CLIMessengerInterface +from chatsky.messengers.common import MessengerInterface +from chatsky.slots.slots import GroupSlot +from chatsky.core.service.group import ServiceGroup, ServiceGroupInitTypes +from chatsky.core.service.extra import ComponentExtraHandlerInitTypes, BeforeHandler, AfterHandler +from chatsky.core.service.types import ( + GlobalExtraHandlerType, + ExtraHandlerFunction, +) +from .service import Service +from .utils import finalize_service_group +from chatsky.core.service.actor import Actor +from chatsky.core.node_label import AbsoluteNodeLabel, AbsoluteNodeLabelInitTypes + +logger = logging.getLogger(__name__) + + +class PipelineServiceGroup(ServiceGroup): + """A service group that allows actor inside.""" + + components: List[Union[Actor, Service, ServiceGroup]] + + +class Pipeline(BaseModel, extra="forbid", arbitrary_types_allowed=True): + """ + Class that automates service execution and creates service pipeline. + """ + + pre_services: ServiceGroup = Field(default_factory=list, validate_default=True) + """ + :py:class:`~.ServiceGroup` that will be executed before Actor. + """ + post_services: ServiceGroup = Field(default_factory=list, validate_default=True) + """ + :py:class:`~.ServiceGroup` that will be executed after :py:class:`~.Actor`. + """ + script: Script + """ + (required) A :py:class:`~.Script` instance (object or dict). + """ + start_label: AbsoluteNodeLabel + """ + (required) The first node of every context. + """ + fallback_label: AbsoluteNodeLabel + """ + Node which will is used if :py:class:`Actor` cannot find the next node. + + This most commonly happens when there are not suitable transitions. + + Defaults to :py:attr:`start_label`. + """ + default_priority: float = 1.0 + """ + Default priority value for :py:class:`~chatsky.core.transition.Transition`. + + Defaults to ``1.0``. + """ + slots: GroupSlot = Field(default_factory=GroupSlot) + """ + Slots configuration. + """ + messenger_interface: MessengerInterface = Field(default_factory=CLIMessengerInterface) + """ + A `MessengerInterface` instance for this pipeline. + + It handles connections to interfaces that provide user requests and accept bot responses. + """ + context_storage: Union[DBContextStorage, Dict] = Field(default_factory=dict) + """ + A :py:class:`~.DBContextStorage` instance for this pipeline or + a dict to store dialog :py:class:`~.Context`. + """ + before_handler: BeforeHandler = Field(default_factory=list, validate_default=True) + """ + :py:class:`~.BeforeHandler` to add to the pipeline service. + """ + after_handler: AfterHandler = Field(default_factory=list, validate_default=True) + """ + :py:class:`~.AfterHandler` to add to the pipeline service. + """ + timeout: Optional[float] = None + """ + Timeout to add to pipeline root service group. + """ + optimization_warnings: bool = False + """ + Asynchronous pipeline optimization check request flag; + warnings will be sent to logs. Additionally, it has some calculated fields: + + - `services_pipeline` is a pipeline root :py:class:`~.ServiceGroup` object, + - `actor` is a pipeline actor, found among services. + + """ + parallelize_processing: bool = False + """ + This flag determines whether or not the functions + defined in the ``PRE_RESPONSE_PROCESSING`` and ``PRE_TRANSITIONS_PROCESSING`` sections + of the script should be parallelized over respective groups. + """ + + def __init__( + self, + script: Union[Script, dict], + start_label: AbsoluteNodeLabelInitTypes, + fallback_label: AbsoluteNodeLabelInitTypes = None, + *, + default_priority: float = None, + slots: GroupSlot = None, + messenger_interface: MessengerInterface = None, + context_storage: Union[DBContextStorage, dict] = None, + pre_services: ServiceGroupInitTypes = None, + post_services: ServiceGroupInitTypes = None, + before_handler: ComponentExtraHandlerInitTypes = None, + after_handler: ComponentExtraHandlerInitTypes = None, + timeout: float = None, + optimization_warnings: bool = None, + parallelize_processing: bool = None, + ): + if fallback_label is None: + fallback_label = start_label + init_dict = { + "script": script, + "start_label": start_label, + "fallback_label": fallback_label, + "default_priority": default_priority, + "slots": slots, + "messenger_interface": messenger_interface, + "context_storage": context_storage, + "pre_services": pre_services, + "post_services": post_services, + "before_handler": before_handler, + "after_handler": after_handler, + "timeout": timeout, + "optimization_warnings": optimization_warnings, + "parallelize_processing": parallelize_processing, + } + empty_fields = set() + for k, v in init_dict.items(): + if k not in self.model_fields: + raise NotImplementedError("Init method contains a field not in model fields.") + if v is None: + empty_fields.add(k) + for field in empty_fields: + del init_dict[field] + super().__init__(**init_dict) + self.services_pipeline # cache services + + @computed_field + @cached_property + def actor(self) -> Actor: + """An actor instance of the pipeline.""" + return Actor() + + @computed_field + @cached_property + def services_pipeline(self) -> PipelineServiceGroup: + """ + A group containing :py:attr:`.Pipeline.pre_services`, :py:class:`~.Actor` + and :py:attr:`.Pipeline.post_services`. + It has :py:attr:`.Pipeline.before_handler` and :py:attr:`.Pipeline.after_handler` applied to it. + """ + components = [self.pre_services, self.actor, self.post_services] + self.pre_services.name = "pre" + self.post_services.name = "post" + services_pipeline = PipelineServiceGroup( + components=components, + before_handler=self.before_handler, + after_handler=self.after_handler, + timeout=self.timeout, + ) + services_pipeline.name = "pipeline" + services_pipeline.path = ".pipeline" + + finalize_service_group(services_pipeline, path=services_pipeline.path) + + if self.optimization_warnings: + services_pipeline.log_optimization_warnings() + + return services_pipeline + + @model_validator(mode="after") + def validate_start_label(self): + """Validate :py:attr:`start_label` is in :py:attr:`script`.""" + if self.script.get_node(self.start_label) is None: + raise ValueError(f"Unknown start_label={self.start_label}") + return self + + @model_validator(mode="after") + def validate_fallback_label(self): + """Validate :py:attr:`fallback_label` is in :py:attr:`script`.""" + if self.script.get_node(self.fallback_label) is None: + raise ValueError(f"Unknown fallback_label={self.fallback_label}") + return self + + def add_global_handler( + self, + global_handler_type: GlobalExtraHandlerType, + extra_handler: ExtraHandlerFunction, + whitelist: Optional[List[str]] = None, + blacklist: Optional[List[str]] = None, + ): + """ + Method for adding global wrappers to pipeline. + Different types of global wrappers are called before/after pipeline execution + or before/after each pipeline component. + They can be used for pipeline statistics collection or other functionality extensions. + NB! Global wrappers are still wrappers, + they shouldn't be used for much time-consuming tasks (see :py:mod:`chatsky.core.service.extra`). + + :param global_handler_type: (required) indication where the wrapper + function should be executed. + :param extra_handler: (required) wrapper function itself. + :type extra_handler: ExtraHandlerFunction + :param whitelist: a list of services to only add this wrapper to. + :param blacklist: a list of services to not add this wrapper to. + :return: `None` + """ + + def condition(name: str) -> bool: + return (whitelist is None or name in whitelist) and (blacklist is None or name not in blacklist) + + if ( + global_handler_type is GlobalExtraHandlerType.BEFORE_ALL + or global_handler_type is GlobalExtraHandlerType.AFTER_ALL + ): + whitelist = ["pipeline"] + global_handler_type = ( + GlobalExtraHandlerType.BEFORE + if global_handler_type is GlobalExtraHandlerType.BEFORE_ALL + else GlobalExtraHandlerType.AFTER + ) + + self.services_pipeline.add_extra_handler(global_handler_type, extra_handler, condition) + + @property + def info_dict(self) -> dict: + """ + Property for retrieving info dictionary about this pipeline. + Returns info dict, containing most important component public fields as well as its type. + All complex or unserializable fields here are replaced with 'Instance of [type]'. + """ + return { + "type": type(self).__name__, + "messenger_interface": f"Instance of {type(self.messenger_interface).__name__}", + "context_storage": f"Instance of {type(self.context_storage).__name__}", + "services": [self.services_pipeline.info_dict], + } + + async def _run_pipeline( + self, request: Message, ctx_id: Optional[Hashable] = None, update_ctx_misc: Optional[dict] = None + ) -> Context: + """ + Method that should be invoked on user input. + This method has the same signature as :py:class:`~chatsky.core.service.types.PipelineRunnerFunction`. + + This method does: + + 1. Retrieve from :py:attr:`context_storage` or initialize context ``ctx_id``. + 2. Update :py:attr:`.Context.misc` with ``update_ctx_misc``. + 3. Set up :py:attr:`.Context.framework_data` fields. + 4. Add ``request`` to the context. + 5. Execute :py:attr:`services_pipeline`. + This includes :py:class:`.Actor` (read :py:meth:`.Actor.run_component` for more information). + 6. Save context in the :py:attr:`context_storage`. + + :return: Modified context ``ctx_id``. + """ + logger.info(f"Running pipeline for context {ctx_id}.") + logger.debug(f"Received request: {request}.") + if ctx_id is None: + ctx = Context.init(self.start_label) + elif isinstance(self.context_storage, DBContextStorage): + ctx = await self.context_storage.get_async(ctx_id, Context.init(self.start_label, id=ctx_id)) + else: + ctx = self.context_storage.get(ctx_id, Context.init(self.start_label, id=ctx_id)) + + if update_ctx_misc is not None: + ctx.misc.update(update_ctx_misc) + + if self.slots is not None: + ctx.framework_data.slot_manager.set_root_slot(self.slots) + + ctx.framework_data.pipeline = self + + ctx.add_request(request) + result = await self.services_pipeline(ctx, self) + + if asyncio.iscoroutine(result): + await result + + ctx.framework_data.service_states.clear() + ctx.framework_data.pipeline = None + + if isinstance(self.context_storage, DBContextStorage): + await self.context_storage.set_item_async(ctx_id, ctx) + else: + self.context_storage[ctx_id] = ctx + + return ctx + + def run(self): + """ + Method that starts a pipeline and connects to :py:attr:`messenger_interface`. + + It passes :py:meth:`_run_pipeline` to :py:attr:`messenger_interface` as a callback, + so every time user request is received, :py:meth:`_run_pipeline` will be called. + + This method can be both blocking and non-blocking. It depends on current :py:attr:`messenger_interface` nature. + Message interfaces that run in a loop block current thread. + """ + logger.info("Pipeline is accepting requests.") + asyncio.run(self.messenger_interface.connect(self._run_pipeline)) + + def __call__( + self, request: Message, ctx_id: Optional[Hashable] = None, update_ctx_misc: Optional[dict] = None + ) -> Context: + """ + Method that executes pipeline once. + Basically, it is a shortcut for :py:meth:`_run_pipeline`. + NB! When pipeline is executed this way, :py:attr:`messenger_interface` won't be initiated nor connected. + + This method has the same signature as :py:class:`~chatsky.core.service.types.PipelineRunnerFunction`. + """ + return asyncio.run(self._run_pipeline(request, ctx_id, update_ctx_misc)) diff --git a/chatsky/core/script.py b/chatsky/core/script.py new file mode 100644 index 000000000..f70db49fe --- /dev/null +++ b/chatsky/core/script.py @@ -0,0 +1,173 @@ +""" +Script +------ +The Script module provides a set of `pydantic` models for representing the dialog graph. + +These models are used by :py:class:`~chatsky.core.service.Actor` to define the conversation flow, +and to determine the appropriate response based on the user's input and the current state of the conversation. +""" + +# %% +from __future__ import annotations +import logging +from typing import List, Optional, Dict + +from pydantic import BaseModel, Field + +from chatsky.core.script_function import AnyResponse, BaseProcessing +from chatsky.core.node_label import AbsoluteNodeLabel +from chatsky.core.transition import Transition + +logger = logging.getLogger(__name__) + + +class Node(BaseModel): + """ + Node is a basic element of the dialog graph. + + Usually used to represent a specific state of a conversation. + """ + + transitions: List[Transition] = Field(default_factory=list) + """List of transitions possible from this node.""" + response: Optional[AnyResponse] = Field(default=None) + """Response produced when this node is entered.""" + pre_transition: Dict[str, BaseProcessing] = Field(default_factory=dict) + """ + A dictionary of :py:class:`.BaseProcessing` functions that are executed before transitions are processed. + Keys of the dictionary act as names for the processing functions. + """ + pre_response: Dict[str, BaseProcessing] = Field(default_factory=dict) + """ + A dictionary of :py:class:`.BaseProcessing` functions that are executed before response is processed. + Keys of the dictionary act as names for the processing functions. + """ + misc: dict = Field(default_factory=dict) + """ + A dictionary that is used to store metadata about the node. + + Can be accessed at runtime via :py:attr:`~chatsky.core.context.Context.current_node`. + """ + + def merge(self, other: Node): + """ + Merge another node into this one: + + - Prepend :py:attr:`transitions` of the other node; + - Replace response if ``other.response`` is not ``None``; + - Update :py:attr:`pre_transition`, :py:attr:`pre_response` and :py:attr:`misc` dictionaries. + """ + self.transitions = [*other.transitions, *self.transitions] + if other.response is not None: + self.response = other.response + self.pre_transition.update(**other.pre_transition) + self.pre_response.update(**other.pre_response) + self.misc.update(**other.misc) + return self + + +class Flow(BaseModel, extra="allow"): + """ + Flow is a collection of nodes. + This is used to group them by a specific purpose. + """ + + local_node: Node = Field(alias="local", default_factory=Node) + """Node from which all other nodes in this Flow inherit properties according to :py:meth:`Node.merge`.""" + __pydantic_extra__: Dict[str, Node] + + @property + def nodes(self) -> Dict[str, Node]: + """ + A dictionary of all non-local nodes in this flow. + + Keys in the dictionary acts as names for the nodes. + """ + return self.__pydantic_extra__ + + def get_node(self, name: str) -> Optional[Node]: + """ + Get node with the ``name``. + + :return: Node or ``None`` if it doesn't exist. + """ + return self.nodes.get(name) + + +class Script(BaseModel, extra="allow"): + """ + A script is a collection of nodes. + It represents an entire dialog graph. + """ + + global_node: Node = Field(alias="global", default_factory=Node) + """Node from which all other nodes in this Script inherit properties according to :py:meth:`Node.merge`.""" + __pydantic_extra__: Dict[str, Flow] + + @property + def flows(self) -> Dict[str, Flow]: + """ + A dictionary of all flows in this script. + + Keys in the dictionary acts as names for the flows. + """ + return self.__pydantic_extra__ + + def get_flow(self, name: str) -> Optional[Flow]: + """ + Get flow with the ``name``. + + :return: Flow or ``None`` if it doesn't exist. + """ + return self.flows.get(name) + + def get_node(self, label: AbsoluteNodeLabel) -> Optional[Node]: + """ + Get node with the ``label``. + + :return: Node or ``None`` if it doesn't exist. + """ + flow = self.get_flow(label.flow_name) + if flow is None: + return None + return flow.get_node(label.node_name) + + def get_inherited_node(self, label: AbsoluteNodeLabel) -> Optional[Node]: + """ + Return a new node that inherits (using :py:meth:`Node.merge`) + properties from :py:attr:`Script.global_node`, :py:attr:`Flow.local_node` + and :py:class`Node`. + + Flow and node are determined by ``label``. + + This is essentially a copy of the node specified by ``label``, + that inherits properties from `global_node` and `local_node`. + + :return: A new node or ``None`` if it doesn't exist. + """ + flow = self.get_flow(label.flow_name) + if flow is None: + return None + node = flow.get_node(label.node_name) + if node is None: + return None + + inheritant_node = Node() + + return inheritant_node.merge(self.global_node).merge(flow.local_node).merge(node) + + +GLOBAL = "global" +"""Key for :py:attr:`~chatsky.core.script.Script.global_node`.""" +LOCAL = "local" +"""Key for :py:attr:`~chatsky.core.script.Flow.local_node`.""" +TRANSITIONS = "transitions" +"""Key for :py:attr:`~chatsky.core.script.Node.transitions`.""" +RESPONSE = "response" +"""Key for :py:attr:`~chatsky.core.script.Node.response`.""" +MISC = "misc" +"""Key for :py:attr:`~chatsky.core.script.Node.misc`.""" +PRE_RESPONSE = "pre_response" +"""Key for :py:attr:`~chatsky.core.script.Node.pre_response`.""" +PRE_TRANSITION = "pre_transition" +"""Key for :py:attr:`~chatsky.core.script.Node.pre_transition`.""" diff --git a/chatsky/core/script_function.py b/chatsky/core/script_function.py new file mode 100644 index 000000000..1c3524621 --- /dev/null +++ b/chatsky/core/script_function.py @@ -0,0 +1,251 @@ +""" +Script Function +--------------- +This module provides base classes for functions used in :py:class:`~chatsky.core.script.Script` instances. + +These functions allow dynamic script configuration and are essential to the scripting process. +""" + +from __future__ import annotations + +from typing import Union, Tuple, ClassVar, Optional +from typing_extensions import Annotated +from abc import abstractmethod, ABC +import logging + +from pydantic import BaseModel, model_validator, Field + +from chatsky.utils.devel import wrap_sync_function_in_async +from chatsky.core.context import Context +from chatsky.core.message import Message, MessageInitTypes +from chatsky.core.node_label import NodeLabel, NodeLabelInitTypes, AbsoluteNodeLabel + + +logger = logging.getLogger(__name__) + + +class BaseScriptFunc(BaseModel, ABC, frozen=True): # generic doesn't work well with sphinx autosummary + """ + Base class for any script function. + + Defines :py:meth:`wrapped_call` that wraps :py:meth:`call` and handles exceptions and types conversions. + """ + + return_type: ClassVar[Union[type, Tuple[type, ...]]] + """Return type of the script function.""" + + @abstractmethod + async def call(self, ctx: Context): + """Implement this to create a custom function.""" + raise NotImplementedError() + + async def wrapped_call(self, ctx: Context, *, info: str = ""): + """ + Exception-safe wrapper for :py:meth:`__call__`. + + :return: An instance of :py:attr:`return_type` if possible. + Otherwise, an ``Exception`` instance detailing what went wrong. + """ + try: + result = await self(ctx) + logger.debug(f"Function {self.__class__.__name__} returned {result!r}. {info}") + return result + except Exception as exc: + logger.warning(f"An exception occurred in {self.__class__.__name__}. {info}", exc_info=exc) + return exc + + async def __call__(self, ctx: Context): + """ + Handle :py:meth:`call`: + + - Call it (regardless of whether it is async); + - Cast returned value to :py:attr:`return_type`. + + :return: An instance of :py:attr:`return_type`. + :raises TypeError: If :py:meth:`call` returned value of incorrect type. + """ + result = await wrap_sync_function_in_async(self.call, ctx) + if not isinstance(self.return_type, tuple) and issubclass(self.return_type, BaseModel): + result = self.return_type.model_validate(result, context={"ctx": ctx}).model_copy(deep=True) + if not isinstance(result, self.return_type): + raise TypeError( + f"Function `call` of {self.__class__.__name__} should return {self.return_type!r}. " + f"Got instead: {result!r}" + ) + return result + + +class ConstScriptFunc(BaseScriptFunc): + """ + Base class for script functions that return a constant value. + """ + + root: None + """Value to return.""" + + async def call(self, ctx: Context): + return self.root + + @model_validator(mode="before") + @classmethod + def validate_value(cls, data): + """Allow instantiating this class from its root value.""" + return {"root": data} + + +class BaseCondition(BaseScriptFunc, ABC): + """ + Base class for condition functions. + + These are used in :py:attr:`chatsky.core.transition.Transition.cnd`. + """ + + return_type: ClassVar[Union[type, Tuple[type, ...]]] = bool + + @abstractmethod + async def call(self, ctx: Context) -> bool: + raise NotImplementedError + + async def wrapped_call(self, ctx: Context, *, info: str = "") -> Union[bool, Exception]: + return await super().wrapped_call(ctx, info=info) + + async def __call__(self, ctx: Context) -> bool: + return await super().__call__(ctx) + + async def is_true(self, ctx: Context, *, info: str = "") -> bool: + """Same as :py:meth:`wrapped_call` but instead of exceptions return ``False``.""" + result = await self.wrapped_call(ctx, info=info) + if isinstance(result, Exception): + return False + return result + + +class ConstCondition(ConstScriptFunc, BaseCondition): + root: bool + + +AnyCondition = Annotated[Union[ConstCondition, BaseCondition], Field(union_mode="left_to_right")] +""" +A type annotation that allows accepting both :py:class:`ConstCondition` and :py:class:`BaseCondition` +while validating :py:class:`ConstCondition` if possible. +""" + + +class BaseResponse(BaseScriptFunc, ABC): + """ + Base class for response functions. + + These are used in :py:attr:`chatsky.core.script.Node.response`. + """ + + return_type: ClassVar[Union[type, Tuple[type, ...]]] = Message + + @abstractmethod + async def call(self, ctx: Context) -> MessageInitTypes: + raise NotImplementedError + + async def wrapped_call(self, ctx: Context, *, info: str = "") -> Union[Message, Exception]: + return await super().wrapped_call(ctx, info=info) + + async def __call__(self, ctx: Context) -> Message: + return await super().__call__(ctx) + + +class ConstResponse(ConstScriptFunc, BaseResponse): + root: Message + + +AnyResponse = Annotated[Union[ConstResponse, BaseResponse], Field(union_mode="left_to_right")] +""" +A type annotation that allows accepting both :py:class:`ConstResponse` and :py:class:`BaseResponse` +while validating :py:class:`ConstResponse` if possible. +""" + + +class BaseDestination(BaseScriptFunc, ABC): + """ + Base class for destination functions. + + These are used in :py:attr:`chatsky.core.transition.Transition.dst`. + """ + + return_type: ClassVar[Union[type, Tuple[type, ...]]] = AbsoluteNodeLabel + + @abstractmethod + async def call(self, ctx: Context) -> NodeLabelInitTypes: + raise NotImplementedError + + async def wrapped_call(self, ctx: Context, *, info: str = "") -> Union[AbsoluteNodeLabel, Exception]: + return await super().wrapped_call(ctx, info=info) + + async def __call__(self, ctx: Context) -> AbsoluteNodeLabel: + return await super().__call__(ctx) + + +class ConstDestination(ConstScriptFunc, BaseDestination): + root: NodeLabel + + +AnyDestination = Annotated[Union[ConstDestination, BaseDestination], Field(union_mode="left_to_right")] +""" +A type annotation that allows accepting both :py:class:`ConstDestination` and :py:class:`BaseDestination` +while validating :py:class:`ConstDestination` if possible. +""" + + +class BaseProcessing(BaseScriptFunc, ABC): + """ + Base class for processing functions. + + These are used in :py:attr:`chatsky.core.script.Node.pre_transition` + and :py:attr:`chatsky.core.script.Node.pre_response`. + """ + + return_type: ClassVar[Union[type, Tuple[type, ...]]] = type(None) + + @abstractmethod + async def call(self, ctx: Context) -> None: + raise NotImplementedError + + async def wrapped_call(self, ctx: Context, *, info: str = "") -> Union[None, Exception]: + return await super().wrapped_call(ctx, info=info) + + async def __call__(self, ctx: Context) -> None: + return await super().__call__(ctx) + + +class BasePriority(BaseScriptFunc, ABC): + """ + Base class for priority functions. + + These are used in :py:attr:`chatsky.core.transition.Transition.priority`. + + Has several possible return types: + + - ``float``: Transition successful with the corresponding priority; + - ``True`` or ``None``: Transition successful with the :py:attr:`~chatsky.core.pipeline.Pipeline.default_priority`; + - ``False``: Transition unsuccessful. + """ + + return_type: ClassVar[Union[type, Tuple[type, ...]]] = (float, type(None), bool) + + @abstractmethod + async def call(self, ctx: Context) -> Union[float, bool, None]: + raise NotImplementedError + + async def wrapped_call(self, ctx: Context, *, info: str = "") -> Union[float, bool, None, Exception]: + return await super().wrapped_call(ctx, info=info) + + async def __call__(self, ctx: Context) -> Union[float, bool, None]: + return await super().__call__(ctx) + + +class ConstPriority(ConstScriptFunc, BasePriority): + root: Optional[float] + + +AnyPriority = Annotated[Union[ConstPriority, BasePriority], Field(union_mode="left_to_right")] +""" +A type annotation that allows accepting both :py:class:`ConstPriority` and :py:class:`BasePriority` +while validating :py:class:`ConstPriority` if possible. +""" diff --git a/chatsky/pipeline/__init__.py b/chatsky/core/service/__init__.py similarity index 55% rename from chatsky/pipeline/__init__.py rename to chatsky/core/service/__init__.py index c6152f31c..500c8dc25 100644 --- a/chatsky/pipeline/__init__.py +++ b/chatsky/core/service/__init__.py @@ -1,30 +1,29 @@ -# -*- coding: utf-8 -*- - +""" +Service +------- +This module defines services -- a way to process context outside the Script. +""" +from .component import PipelineComponent from .conditions import ( always_start_condition, service_successful_condition, not_condition, - aggregate_condition, all_condition, any_condition, ) +from .extra import BeforeHandler, AfterHandler +from .group import ServiceGroup +from .service import Service, to_service from .types import ( - ComponentExecutionState, + ServiceRuntimeInfo, + ExtraHandlerRuntimeInfo, GlobalExtraHandlerType, ExtraHandlerType, + PipelineRunnerFunction, + ComponentExecutionState, StartConditionCheckerFunction, - StartConditionCheckerAggregationFunction, ExtraHandlerConditionFunction, - ServiceRuntimeInfo, - ExtraHandlerRuntimeInfo, ExtraHandlerFunction, ServiceFunction, ) - -from .service.extra import BeforeHandler, AfterHandler, ComponentExtraHandler -from .service.service import Service, to_service -from .service.group import ServiceGroup - -from .pipeline.actor import Actor -from .pipeline.pipeline import Pipeline diff --git a/chatsky/core/service/actor.py b/chatsky/core/service/actor.py new file mode 100644 index 000000000..3646e580d --- /dev/null +++ b/chatsky/core/service/actor.py @@ -0,0 +1,134 @@ +""" +Actor +----- +Actor is a component of :py:class:`.Pipeline`, that processes the :py:class:`.Script`. + +It is responsible for determining the next node and getting response from it. + +The actor acts as a bridge between the user's input and the dialog graph, +making sure that the conversation follows the expected flow. + +More details on the processing can be found in the documentation for +:py:meth:`Actor.run_component`. +""" + +from __future__ import annotations +import logging +import asyncio +from typing import TYPE_CHECKING, Dict +from pydantic import model_validator + +from chatsky.core.service.component import PipelineComponent +from chatsky.core.transition import get_next_label +from chatsky.core.message import Message + +from chatsky.core.context import Context +from chatsky.core.script_function import BaseProcessing + +if TYPE_CHECKING: + from chatsky.core.pipeline import Pipeline + +logger = logging.getLogger(__name__) + + +class Actor(PipelineComponent): + """ + The class which is used to process :py:class:`~chatsky.core.context.Context` + according to the :py:class:`~chatsky.core.script.Script`. + """ + + @model_validator(mode="after") + def __tick_async_flag__(self): + self.calculated_async_flag = False + return self + + @property + def computed_name(self) -> str: + return "actor" + + async def run_component(self, ctx: Context, pipeline: Pipeline) -> None: + """ + Process the context in the following way: + + 1. Run pre-transition of the :py:attr:`.Context.current_node`. + 2. Determine and save the next node based on :py:attr:`~chatsky.core.script.Node.transitions` + of the :py:attr:`.Context.current_node`. + 3. Run pre-response of the :py:attr:`.Context.current_node`. + 4. Determine and save the response of the :py:attr:`.Context.current_node` + """ + next_label = pipeline.fallback_label + + try: + ctx.framework_data.current_node = pipeline.script.get_inherited_node(ctx.last_label) + + logger.debug("Running pre_transition") + await self._run_processing(ctx.current_node.pre_transition, ctx) + + logger.debug("Running transitions") + + destination_result = await get_next_label(ctx, ctx.current_node.transitions, pipeline.default_priority) + if destination_result is not None: + next_label = destination_result + except Exception as exc: + logger.exception("Exception occurred during transition processing.", exc_info=exc) + + logger.debug(f"Next label: {next_label}") + + ctx.add_label(next_label) + + response = Message() + + try: + ctx.framework_data.current_node = pipeline.script.get_inherited_node(next_label) + + logger.debug("Running pre_response") + await self._run_processing(ctx.current_node.pre_response, ctx) + + node_response = ctx.current_node.response + if node_response is not None: + response_result = await node_response.wrapped_call(ctx) + if isinstance(response_result, Message): + response = response_result + logger.debug(f"Produced response {response}.") + else: + logger.debug("Response was not produced.") + else: + logger.debug("Node has empty response.") + except Exception as exc: + logger.exception("Exception occurred during response processing.", exc_info=exc) + + ctx.add_response(response) + + @staticmethod + async def _run_processing_parallel(processing: Dict[str, BaseProcessing], ctx: Context) -> None: + """ + Execute :py:class:`.BaseProcessing` functions simultaneously, independent of the order. + + Picked depending on the value of the :py:class:`.Pipeline`'s `parallelize_processing` flag. + """ + await asyncio.gather( + *[func.wrapped_call(ctx, info=f"processing_name={name!r}") for name, func in processing.items()] + ) + + @staticmethod + async def _run_processing_sequential(processing: Dict[str, BaseProcessing], ctx: Context) -> None: + """ + Execute :py:class:`.BaseProcessing` functions in-order. + + Picked depending on the value of the :py:class:`.Pipeline`'s `parallelize_processing` flag. + """ + for name, func in processing.items(): + await func.wrapped_call(ctx, info=f"processing_name={name!r}") + + @staticmethod + async def _run_processing(processing: Dict[str, BaseProcessing], ctx: Context) -> None: + """ + Run :py:class:`.BaseProcessing` functions. + + The execution order depends on the value of the :py:class:`.Pipeline`'s + `parallelize_processing` flag. + """ + if ctx.pipeline.parallelize_processing: + await Actor._run_processing_parallel(processing, ctx) + else: + await Actor._run_processing_sequential(processing, ctx) diff --git a/chatsky/pipeline/pipeline/component.py b/chatsky/core/service/component.py similarity index 81% rename from chatsky/pipeline/pipeline/component.py rename to chatsky/core/service/component.py index 362530432..fea10809b 100644 --- a/chatsky/pipeline/pipeline/component.py +++ b/chatsky/core/service/component.py @@ -1,12 +1,9 @@ """ Component --------- -The Component module defines a :py:class:`.PipelineComponent` class, -which is a fundamental building block of the framework. A PipelineComponent represents a single -step in a processing pipeline, and is responsible for performing a specific task or set of tasks. +The Component module defines a :py:class:`.PipelineComponent` class. -The PipelineComponent class can be a group or a service. It is designed to be reusable and composable, -allowing developers to create complex processing pipelines by combining multiple components. +This is a base class for pipeline processing and is responsible for performing a specific task. """ from __future__ import annotations @@ -16,11 +13,9 @@ from typing import Optional, Awaitable, TYPE_CHECKING from pydantic import BaseModel, Field, model_validator -from chatsky.script import Context - -from ..service.extra import BeforeHandler, AfterHandler -from ..conditions import always_start_condition -from ..types import ( +from chatsky.core.service.extra import BeforeHandler, AfterHandler +from chatsky.core.service.conditions import always_start_condition +from chatsky.core.service.types import ( StartConditionCheckerFunction, ComponentExecutionState, ServiceRuntimeInfo, @@ -33,13 +28,13 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline + from chatsky.core.pipeline import Pipeline + from chatsky.core.context import Context class PipelineComponent(abc.ABC, BaseModel, extra="forbid", arbitrary_types_allowed=True): """ - This class represents a pipeline component, which is a service or a service group. - It contains some fields that they have in common. + Base class for a single task processed by :py:class:`.Pipeline`. """ before_handler: BeforeHandler = Field(default_factory=BeforeHandler) @@ -58,24 +53,20 @@ class PipelineComponent(abc.ABC, BaseModel, extra="forbid", arbitrary_types_allo requested_async_flag: Optional[bool] = None """ Requested asynchronous property; if not defined, - :py:attr:`~.PipelineComponent.calculated_async_flag` is used instead. + :py:attr:`~PipelineComponent.calculated_async_flag` is used instead. """ calculated_async_flag: bool = False """ Whether the component can be asynchronous or not. - - 1) for :py:class:`~.pipeline.service.service.Service`: whether its `handler` is asynchronous or not, - 2) for :py:class:`~.pipeline.service.group.ServiceGroup`: whether all its `services` are asynchronous or not. - """ start_condition: StartConditionCheckerFunction = Field(default=always_start_condition) """ - :py:class:`~.pipeline.types.StartConditionCheckerFunction` that is invoked before each component execution; - component is executed only if it returns `True`. + :py:data:`.StartConditionCheckerFunction` that is invoked before each component execution; + component is executed only if it returns ``True``. """ name: Optional[str] = None """ - Component name (should be unique in single :py:class:`~.pipeline.service.group.ServiceGroup`), + Component name (should be unique in a single :py:class:`~chatsky.core.service.group.ServiceGroup`), should not be blank or contain the ``.`` character. """ path: Optional[str] = None @@ -103,22 +94,21 @@ def __pipeline_component_validator__(self): def _set_state(self, ctx: Context, value: ComponentExecutionState): """ - Method for component runtime state setting, state is preserved in `ctx.framework_data`. + Method for component runtime state setting, state is preserved in :py:attr:`.Context.framework_data`. :param ctx: :py:class:`~.Context` to keep state in. :param value: State to set. - :return: `None` """ ctx.framework_data.service_states[self.path] = value def get_state(self, ctx: Context, default: Optional[ComponentExecutionState] = None) -> ComponentExecutionState: """ - Method for component runtime state getting, state is preserved in `ctx.framework_data`. + Method for component runtime state getting, state is preserved in :py:attr:`.Context.framework_data`. :param ctx: :py:class:`~.Context` to get state from. :param default: Default to return if no record found - (usually it's :py:attr:`~.pipeline.types.ComponentExecutionState.NOT_RUN`). - :return: :py:class:`~pipeline.types.ComponentExecutionState` of this service or default if not found. + (usually it's :py:attr:`~.ComponentExecutionState.NOT_RUN`). + :return: :py:class:`.ComponentExecutionState` of this service or default if not found. """ return ctx.framework_data.service_states.get(self.path, default if default is not None else None) @@ -169,15 +159,15 @@ async def run_component(self, ctx: Context, pipeline: Pipeline) -> Optional[Comp def computed_name(self) -> str: """ Default name that is used if :py:attr:`~.PipelineComponent.name` is not defined. - In case two components in a :py:class:`~.ServiceGroup` have the same - :py:attr:`~.PipelineComponent.computed_name` an incrementing number is appended to the name. + In case two components in a :py:class:`~chatsky.core.service.group.ServiceGroup` have the same + :py:attr:`.computed_name` an incrementing number is appended to the name. """ return "noname_service" async def _run(self, ctx: Context, pipeline: Pipeline) -> None: """ A method for running a pipeline component. Executes extra handlers before and after execution, - launches `run_component` method. This method is run after the component's timeout is set (if needed). + launches :py:meth:`.run_component` method. This method is run after the component's timeout is set (if needed). :param ctx: Current dialog :py:class:`~.Context`. :param pipeline: This :py:class:`~.Pipeline`. @@ -200,11 +190,11 @@ async def _run(self, ctx: Context, pipeline: Pipeline) -> None: async def __call__(self, ctx: Context, pipeline: Pipeline) -> Optional[Awaitable]: """ A method for calling pipeline components. - It sets up timeout if this component is asynchronous and executes it using :py:meth:`~._run` method. + It sets up timeout if this component is asynchronous and executes it using :py:meth:`_run` method. :param ctx: Current dialog :py:class:`~.Context`. :param pipeline: This :py:class:`~.Pipeline`. - :return: `None` if the service is synchronous; an `Awaitable` otherwise. + :return: ``None`` if the service is synchronous; an ``Awaitable`` otherwise. """ if self.asynchronous: task = asyncio.create_task(self._run(ctx, pipeline)) @@ -231,7 +221,7 @@ def _get_runtime_info(self, ctx: Context) -> ServiceRuntimeInfo: Method for retrieving runtime info about this component. :param ctx: Current dialog :py:class:`~.Context`. - :return: :py:class:`~.chatsky.script.typing.ServiceRuntimeInfo` + :return: :py:class:`.ServiceRuntimeInfo` object where all not set fields are replaced with `[None]`. """ return ServiceRuntimeInfo( diff --git a/chatsky/pipeline/conditions.py b/chatsky/core/service/conditions.py similarity index 88% rename from chatsky/pipeline/conditions.py rename to chatsky/core/service/conditions.py index 01a5acb45..e5560336b 100644 --- a/chatsky/pipeline/conditions.py +++ b/chatsky/core/service/conditions.py @@ -1,24 +1,24 @@ """ Conditions ---------- -The conditions module contains functions that can be used to determine whether the pipeline component to which they -are attached should be executed or not. -The standard set of them allows user to setup dependencies between pipeline components. +The conditions module contains functions that determine whether the pipeline component should be executed or not. + +The standard set of them allows user to set up dependencies between pipeline components. """ from __future__ import annotations from typing import Optional, TYPE_CHECKING -from chatsky.script import Context +from chatsky.core.context import Context -from .types import ( +from chatsky.core.service.types import ( StartConditionCheckerFunction, ComponentExecutionState, StartConditionCheckerAggregationFunction, ) if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline + from chatsky.core.pipeline import Pipeline def always_start_condition(_: Context, __: Pipeline) -> bool: diff --git a/chatsky/pipeline/service/extra.py b/chatsky/core/service/extra.py similarity index 90% rename from chatsky/pipeline/service/extra.py rename to chatsky/core/service/extra.py index f7475ee58..a6bf4eec9 100644 --- a/chatsky/pipeline/service/extra.py +++ b/chatsky/core/service/extra.py @@ -1,22 +1,22 @@ """ Extra Handler ------------- -The Extra Handler module contains additional functionality that extends the capabilities of the system -beyond the core functionality. Extra handlers is an input converting addition to :py:class:`.PipelineComponent`. -For example, it is used to grep statistics from components, timing, logging, etc. +Extra handlers are functions that are executed before or after a +:py:class:`~chatsky.core.service.component.PipelineComponent`. """ from __future__ import annotations import asyncio import logging import inspect -from typing import Optional, List, TYPE_CHECKING, Any, ClassVar +from typing import Optional, List, TYPE_CHECKING, Any, ClassVar, Union, Callable +from typing_extensions import Annotated, TypeAlias from pydantic import BaseModel, computed_field, model_validator, Field -from chatsky.script import Context +from chatsky.core.context import Context from chatsky.utils.devel.async_helpers import wrap_sync_function_in_async -from ..types import ( +from chatsky.core.service.types import ( ServiceRuntimeInfo, ExtraHandlerType, ExtraHandlerFunction, @@ -26,12 +26,13 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline + from chatsky.core.pipeline import Pipeline class ComponentExtraHandler(BaseModel, extra="forbid", arbitrary_types_allowed=True): """ - Class, representing an extra pipeline component handler. + Class, representing an extra handler for pipeline components. + A component extra handler is a set of functions, attached to pipeline component (before or after it). Extra handlers should execute supportive tasks (like time or resources measurement, minor data transformations). Extra handlers should NOT edit context or pipeline, use services for that purpose instead. @@ -201,3 +202,12 @@ class AfterHandler(ComponentExtraHandler): """ stage: ClassVar[ExtraHandlerType] = ExtraHandlerType.AFTER + + +ComponentExtraHandlerInitTypes: TypeAlias = Union[ + ComponentExtraHandler, + Annotated[dict, "dict following the ComponentExtraHandler data model"], + Annotated[Callable, "a singular function for the extra handler"], + Annotated[List[Callable], "functions for the extra handler"], +] +"""Types that :py:class:`~.ComponentExtraHandler` can be validated from.""" diff --git a/chatsky/pipeline/service/group.py b/chatsky/core/service/group.py similarity index 87% rename from chatsky/pipeline/service/group.py rename to chatsky/core/service/group.py index 8c2efc37a..85b9d3165 100644 --- a/chatsky/pipeline/service/group.py +++ b/chatsky/core/service/group.py @@ -1,44 +1,46 @@ """ Service Group ------------- -The Service Group module contains the -:py:class:`~.ServiceGroup` class, which is used to represent a group of related services. +The Service Group module contains the ServiceGroup class, which is used to represent a group of related services. + This class provides a way to organize and manage multiple services as a single unit, allowing for easier management and organization of the services within the pipeline. -The :py:class:`~.ServiceGroup` serves the important function of grouping services to work together in parallel. + +:py:class:`~.ServiceGroup` serves the important function of grouping services to work together in parallel. """ from __future__ import annotations import asyncio import logging from typing import List, Union, Awaitable, TYPE_CHECKING, Any, Optional +from typing_extensions import TypeAlias, Annotated -from chatsky.pipeline import BeforeHandler, AfterHandler, always_start_condition from pydantic import model_validator, Field -from chatsky.script import Context -from ..pipeline.actor import Actor - -from ..pipeline.component import PipelineComponent -from ..types import ( +from chatsky.core.service.extra import BeforeHandler, AfterHandler +from chatsky.core.service.conditions import always_start_condition +from chatsky.core.context import Context +from chatsky.core.service.actor import Actor +from chatsky.core.service.component import PipelineComponent +from chatsky.core.service.types import ( ComponentExecutionState, GlobalExtraHandlerType, ExtraHandlerConditionFunction, ExtraHandlerFunction, StartConditionCheckerFunction, ) -from .service import Service +from .service import Service, ServiceInitTypes logger = logging.getLogger(__name__) if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline + from chatsky.core.pipeline import Pipeline class ServiceGroup(PipelineComponent): """ A service group class. - Service group can be included into pipeline as an object or a pipeline component list. + Service group can be synchronous or asynchronous. Components in synchronous groups are executed consequently (no matter is they are synchronous or asynchronous). Components in asynchronous groups are executed simultaneously. @@ -47,7 +49,6 @@ class ServiceGroup(PipelineComponent): components: List[ Union[ - Actor, Service, ServiceGroup, ] @@ -194,3 +195,12 @@ def info_dict(self) -> dict: representation = super(ServiceGroup, self).info_dict representation.update({"services": [service.info_dict for service in self.components]}) return representation + + +ServiceGroupInitTypes: TypeAlias = Union[ + ServiceGroup, + Annotated[List[Union[Actor, ServiceInitTypes, "ServiceGroupInitTypes"]], "list of components"], + Annotated[Union[Actor, ServiceInitTypes, "ServiceGroupInitTypes"], "single component of the group"], + Annotated[dict, "dict following the ServiceGroup data model"], +] +"""Types that :py:class:`~.ServiceGroup` can be validated from.""" diff --git a/chatsky/pipeline/service/service.py b/chatsky/core/service/service.py similarity index 87% rename from chatsky/pipeline/service/service.py rename to chatsky/core/service/service.py index 1796ae6b3..cba20e0bd 100644 --- a/chatsky/pipeline/service/service.py +++ b/chatsky/core/service/service.py @@ -1,11 +1,11 @@ """ Service ------- -The Service module contains the :py:class:`.Service` class, -which can be included into pipeline as object or a dictionary. +The Service module contains the :py:class:`.Service` class which represents a single task. + Pipeline consists of services and service groups. -Service group can be synchronous or asynchronous. Service is an atomic part of a pipeline. + Service can be asynchronous only if its handler is a coroutine. Actor wrapping service is asynchronous. """ @@ -13,32 +13,30 @@ from __future__ import annotations import logging import inspect -from typing import TYPE_CHECKING, Any, Optional, Callable +from typing import TYPE_CHECKING, Any, Optional, Callable, Union +from typing_extensions import TypeAlias, Annotated from pydantic import model_validator, Field -from chatsky.script import Context - - +from chatsky.core.context import Context from chatsky.utils.devel.async_helpers import wrap_sync_function_in_async -from chatsky.pipeline import always_start_condition -from ..types import ( +from chatsky.core.service.conditions import always_start_condition +from chatsky.core.service.types import ( ServiceFunction, StartConditionCheckerFunction, ) -from ..pipeline.component import PipelineComponent +from chatsky.core.service.component import PipelineComponent from .extra import BeforeHandler, AfterHandler logger = logging.getLogger(__name__) if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline + from chatsky.core.pipeline import Pipeline class Service(PipelineComponent): """ This class represents a service. - Service can be included into pipeline as object or a dictionary. - Service group can be synchronous or asynchronous. + Service can be asynchronous only if its handler is a coroutine. """ @@ -145,3 +143,9 @@ def inner(handler: ServiceFunction) -> Service: ) return inner + + +ServiceInitTypes: TypeAlias = Union[ + Service, Annotated[dict, "dict following the Service data model"], Annotated[Callable, "handler for the service"] +] +"""Types that :py:class:`~.Service` can be validated from.""" diff --git a/chatsky/pipeline/types.py b/chatsky/core/service/types.py similarity index 95% rename from chatsky/pipeline/types.py rename to chatsky/core/service/types.py index 56e955855..d17d4dad1 100644 --- a/chatsky/pipeline/types.py +++ b/chatsky/core/service/types.py @@ -1,7 +1,8 @@ """ Types ----- -The Types module contains several classes and special types that are used throughout `Chatsky Pipeline`. +This module defines type aliases used throughout the ``Core.Service`` module. + The classes and special types in this module can include data models, data structures, and other types that are defined for type hinting. """ @@ -14,8 +15,7 @@ if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline - from chatsky.script import Context, Message + from chatsky.core import Context, Message, Pipeline class PipelineRunnerFunction(Protocol): @@ -48,7 +48,7 @@ def __call__( class ComponentExecutionState(str, Enum): """ Enum, representing pipeline component execution state. - These states are stored in `ctx.framework_keys.service_states`, + These states are stored in :py:attr:`~chatsky.core.context.FrameworkData.service_states`, that should always be requested with `NOT_RUN` being default fallback. Following states are supported: diff --git a/chatsky/core/transition.py b/chatsky/core/transition.py new file mode 100644 index 000000000..a0ac2aef4 --- /dev/null +++ b/chatsky/core/transition.py @@ -0,0 +1,102 @@ +""" +Transition +---------- +This module defines a transition class that is used to +specify conditions and destinations for transitions to nodes. +""" + +from __future__ import annotations + +from typing import Union, List, TYPE_CHECKING, Optional, Tuple +import logging +import asyncio + +from pydantic import BaseModel, Field + +from chatsky.core.script_function import AnyCondition, AnyDestination, AnyPriority +from chatsky.core.script_function import BaseCondition, BaseDestination, BasePriority +from chatsky.core.node_label import AbsoluteNodeLabel, NodeLabelInitTypes + +if TYPE_CHECKING: + from chatsky.core.context import Context + + +logger = logging.getLogger(__name__) + + +class Transition(BaseModel): + """ + A basic class for a transition to a node. + """ + + cnd: AnyCondition = Field(default=True, validate_default=True) + """A condition that determines if transition is allowed to happen.""" + dst: AnyDestination + """Destination node of the transition.""" + priority: AnyPriority = Field(default=None, validate_default=True) + """Priority of the transition. Higher priority transitions are resolved first.""" + + def __init__( + self, + *, + cnd: Union[bool, BaseCondition] = True, + dst: Union[NodeLabelInitTypes, BaseDestination], + priority: Union[Optional[float], BasePriority] = None, + ): + super().__init__(cnd=cnd, dst=dst, priority=priority) + + +async def get_next_label( + ctx: Context, transitions: List[Transition], default_priority: float +) -> Optional[AbsoluteNodeLabel]: + """ + Determine the next node based on ``transitions`` and ``ctx``. + + The process is as follows: + + 1. Condition result is calculated for every transition. + 2. Transitions are filtered by the calculated condition. + 3. Priority result is calculated for every transition that is left. + ``default_priority`` is used for priorities that return ``True`` or ``None`` + as per :py:class:`.BasePriority`. + Those that return ``False`` are filtered out. + 4. Destination result is calculated for every transition that is left. + 5. The highest priority transition is chosen. + If there are multiple transition of the higher priority, + choose the first one of that priority in the ``transitions`` list. + Order of ``transitions`` is as follows: + ``node transitions, local transitions, global transitions``. + + If at any point any :py:class:`.BaseCondition`, :py:class:`.BaseDestination` or :py:class:`.BasePriority` + produces an exception, the corresponding transition is filtered out. + + :return: Label of the next node or ``None`` if no transition is left by the end of the process. + """ + filtered_transitions: List[Transition] = transitions.copy() + condition_results = await asyncio.gather(*[transition.cnd.wrapped_call(ctx) for transition in filtered_transitions]) + + filtered_transitions = [ + transition for transition, condition in zip(filtered_transitions, condition_results) if condition is True + ] + + priority_results = await asyncio.gather( + *[transition.priority.wrapped_call(ctx) for transition in filtered_transitions] + ) + + transitions_with_priorities: List[Tuple[Transition, float]] = [ + (transition, (priority_result if isinstance(priority_result, float) else default_priority)) + for transition, priority_result in zip(filtered_transitions, priority_results) + if (priority_result is True or priority_result is None or isinstance(priority_result, float)) + ] + logger.debug(f"Possible transitions: {transitions_with_priorities!r}") + + transitions_with_priorities = sorted(transitions_with_priorities, key=lambda x: x[1], reverse=True) + + destination_results = await asyncio.gather( + *[transition.dst.wrapped_call(ctx) for transition, _ in transitions_with_priorities] + ) + + for destination in destination_results: + if isinstance(destination, AbsoluteNodeLabel): + return destination + return None diff --git a/chatsky/pipeline/pipeline/utils.py b/chatsky/core/utils.py similarity index 73% rename from chatsky/pipeline/pipeline/utils.py rename to chatsky/core/utils.py index 931bade56..87f2aba52 100644 --- a/chatsky/pipeline/pipeline/utils.py +++ b/chatsky/core/utils.py @@ -1,29 +1,26 @@ """ Utils ----- -The Utils module contains several service functions that are commonly used throughout the framework. -These functions provide a variety of utility functionality. +The Utils module contains functions used to provide names to nameless pipeline components inside of a group. """ import collections from typing import List -from .component import PipelineComponent -from ..service.group import ServiceGroup +from .service.component import PipelineComponent +from .service.group import ServiceGroup def rename_component_incrementing(component: PipelineComponent, collisions: List[PipelineComponent]) -> str: """ Function for generating new name for a pipeline component, that has similar name with other components in the same group. + The name is generated according to these rules: - - If component is an `Actor`, it is named `actor`. - - If component is a `Service` and the service's handler is `Callable`, it is named after this `callable`. - - If it's a service group, it is named `service_group`. - - Otherwise, it is named `noname_service`. - - | After that, `_[NUMBER]` is added to the resulting name, - where `_[NUMBER]` is number of components with the same name in current service group. + 1. Base name is :py:attr:`.PipelineComponent.computed_name`; + 2. After that, ``_[NUMBER]`` is added to the resulting name, + where ``_[NUMBER]`` is number of components with the same name in current service group. :param component: Component to be renamed. :param collisions: Components in the same service group as component. diff --git a/chatsky/destinations/__init__.py b/chatsky/destinations/__init__.py new file mode 100644 index 000000000..e509fb0f3 --- /dev/null +++ b/chatsky/destinations/__init__.py @@ -0,0 +1 @@ +from .standard import FromHistory, Current, Previous, Start, Fallback, Forward, Backward diff --git a/chatsky/destinations/standard.py b/chatsky/destinations/standard.py new file mode 100644 index 000000000..59115a6e8 --- /dev/null +++ b/chatsky/destinations/standard.py @@ -0,0 +1,143 @@ +""" +Standard Destinations +--------------------- +This module provides basic destinations. + +- :py:class:`FromHistory`, :py:class:`Current` and :py:class:`Previous` -- history-based destinations; +- :py:class:`Start` and :py:class:`Fallback` -- config-based destinations; +- :py:class:`Forward` and :py:class:`Backward` -- script-based destinations. +""" + +from __future__ import annotations + +from pydantic import Field + +from chatsky.core.context import get_last_index, Context +from chatsky.core.node_label import NodeLabelInitTypes, AbsoluteNodeLabel +from chatsky.core.script_function import BaseDestination + + +class FromHistory(BaseDestination): + """ + Return label of the node located at a certain position in the label history. + """ + + position: int = Field(le=-1) + """ + Position of the label in label history. + + Should be negative: + + - ``-1`` refers to the current node (same as ``ctx.last_label``); + - ``-2`` -- to the previous node. + """ + + async def call(self, ctx: Context) -> NodeLabelInitTypes: + index = get_last_index(ctx.labels) + shifted_index = index + self.position + 1 + result = ctx.labels.get(shifted_index) + if result is None: + raise KeyError( + f"No label with index {shifted_index!r}. " + f"Current label index: {index!r}; FromHistory.position: {self.position!r}." + ) + return result + + +class Current(FromHistory): + """ + Return label of the current node. + """ + + position: int = -1 + """Position is set to ``-1`` to get the last node.""" + + +class Previous(FromHistory): + """ + Return label of the previous node. + """ + + position: int = -2 + """Position is set to ``-2`` to get the second to last node.""" + + +class Start(BaseDestination): + """ + Return :py:attr:`~chatsky.core.pipeline.Pipeline.start_label`. + """ + + async def call(self, ctx: Context) -> NodeLabelInitTypes: + return ctx.pipeline.start_label + + +class Fallback(BaseDestination): + """ + Return :py:attr:`~chatsky.core.pipeline.Pipeline.fallback_label`. + """ + + async def call(self, ctx: Context) -> NodeLabelInitTypes: + return ctx.pipeline.fallback_label + + +def get_next_node_in_flow( + node_label: AbsoluteNodeLabel, + ctx: Context, + *, + increment: bool = True, + loop: bool = False, +) -> AbsoluteNodeLabel: + """ + Function that returns node label of a node in the same flow after shifting the index. + + :param node_label: Label of the node to shift from. + :param ctx: Dialog context. + :param increment: If it is `True`, label index is incremented by `1`, + otherwise it is decreased by `1`. + :param loop: If it is `True` the iteration over the label list is going cyclically + (i.e. Backward in the first node returns the last node). + :return: The tuple that consists of `(flow_label, label, priority)`. + If fallback is executed `(flow_fallback_label, fallback_label, priority)` are returned. + """ + node_label = AbsoluteNodeLabel.model_validate(node_label, context={"ctx": ctx}) + node_keys = list(ctx.pipeline.script.get_flow(node_label.flow_name).nodes.keys()) + + node_index = node_keys.index(node_label.node_name) + node_index = node_index + 1 if increment else node_index - 1 + if not (loop or (0 <= node_index < len(node_keys))): + raise IndexError( + f"Node index {node_index!r} out of range for node_keys: {node_keys!r}. Consider using the `loop` flag." + ) + node_index %= len(node_keys) + + return AbsoluteNodeLabel(flow_name=node_label.flow_name, node_name=node_keys[node_index]) + + +class Forward(BaseDestination): + """ + Return the next node relative to the current node in the current flow. + """ + + loop: bool = False + """ + Whether to return the first node of the flow if the current node is the last one. + Otherwise and exception is raised (and transition is considered unsuccessful). + """ + + async def call(self, ctx: Context) -> NodeLabelInitTypes: + return get_next_node_in_flow(ctx.last_label, ctx, increment=True, loop=self.loop) + + +class Backward(BaseDestination): + """ + Return the previous node relative to the current node in the current flow. + """ + + loop: bool = False + """ + Whether to return the last node of the flow if the current node is the first one. + Otherwise and exception is raised (and transition is considered unsuccessful). + """ + + async def call(self, ctx: Context) -> NodeLabelInitTypes: + return get_next_node_in_flow(ctx.last_label, ctx, increment=False, loop=self.loop) diff --git a/chatsky/messengers/common/interface.py b/chatsky/messengers/common/interface.py index 634fad973..cb50a70dc 100644 --- a/chatsky/messengers/common/interface.py +++ b/chatsky/messengers/common/interface.py @@ -14,10 +14,10 @@ from typing import Optional, Any, List, Tuple, Hashable, TYPE_CHECKING, Type if TYPE_CHECKING: - from chatsky.script import Context, Message - from chatsky.pipeline.types import PipelineRunnerFunction + from chatsky.core import Context + from chatsky.core.service.types import PipelineRunnerFunction from chatsky.messengers.common.types import PollingInterfaceLoopFunction - from chatsky.script.core.message import Attachment + from chatsky.core.message import Message, Attachment logger = logging.getLogger(__name__) @@ -35,7 +35,7 @@ async def connect(self, pipeline_runner: PipelineRunnerFunction): May be used for sending an introduction message or displaying general bot information. :param pipeline_runner: A function that should process user request and return context; - usually it's a :py:meth:`~chatsky.pipeline.pipeline.pipeline.Pipeline._run_pipeline` function. + usually it's a :py:meth:`~chatsky.core.pipeline.Pipeline._run_pipeline` function. """ raise NotImplementedError @@ -150,7 +150,7 @@ async def connect( for most cases the loop itself shouldn't be overridden. :param pipeline_runner: A function that should process user request and return context; - usually it's a :py:meth:`~chatsky.pipeline.pipeline.pipeline.Pipeline._run_pipeline` function. + usually it's a :py:meth:`~chatsky.core.pipeline.Pipeline._run_pipeline` function. :param loop: a function that determines whether polling should be continued; called in each cycle, should return `True` to continue polling or `False` to stop. :param timeout: a time interval between polls (in seconds). @@ -180,7 +180,7 @@ async def on_request_async( ) -> Context: """ Method that should be invoked on user input. - This method has the same signature as :py:class:`~chatsky.pipeline.types.PipelineRunnerFunction`. + This method has the same signature as :py:class:`~chatsky.core.service.types.PipelineRunnerFunction`. """ return await self._pipeline_runner(request, ctx_id, update_ctx_misc) @@ -189,6 +189,6 @@ def on_request( ) -> Context: """ Method that should be invoked on user input. - This method has the same signature as :py:class:`~chatsky.pipeline.types.PipelineRunnerFunction`. + This method has the same signature as :py:class:`~chatsky.core.service.types.PipelineRunnerFunction`. """ return asyncio.run(self.on_request_async(request, ctx_id, update_ctx_misc)) diff --git a/chatsky/messengers/common/types.py b/chatsky/messengers/common/types.py index 35696805e..f7a14e59b 100644 --- a/chatsky/messengers/common/types.py +++ b/chatsky/messengers/common/types.py @@ -10,6 +10,6 @@ PollingInterfaceLoopFunction: TypeAlias = Callable[[], bool] """ -A function type used in :py:class:`~.PollingMessengerInterface` to control polling loop. -Returns boolean (whether polling should be continued). +A function type used in :py:class:`~chatsky.messengers.common.interface.PollingMessengerInterface` +to control polling loop. Returns boolean (whether polling should be continued). """ diff --git a/chatsky/messengers/console.py b/chatsky/messengers/console.py index a0fe8c690..b7d1beb1f 100644 --- a/chatsky/messengers/console.py +++ b/chatsky/messengers/console.py @@ -1,9 +1,9 @@ from typing import Any, Hashable, List, Optional, TextIO, Tuple from uuid import uuid4 from chatsky.messengers.common.interface import PollingMessengerInterface -from chatsky.pipeline.types import PipelineRunnerFunction -from chatsky.script.core.context import Context -from chatsky.script.core.message import Message +from chatsky.core.service.types import PipelineRunnerFunction +from chatsky.core.context import Context +from chatsky.core.message import Message class CLIMessengerInterface(PollingMessengerInterface): @@ -30,17 +30,17 @@ def __init__( self._descriptor: Optional[TextIO] = out_descriptor def _request(self) -> List[Tuple[Message, Any]]: - return [(Message(input(self._prompt_request)), self._ctx_id)] + return [(Message(text=input(self._prompt_request)), self._ctx_id)] def _respond(self, responses: List[Context]): - print(f"{self._prompt_response}{responses[0].last_response.text}", file=self._descriptor) + print(f"{self._prompt_response}{responses[0].last_response}", file=self._descriptor) async def connect(self, pipeline_runner: PipelineRunnerFunction, **kwargs): """ The CLIProvider generates new dialog id used to user identification on each `connect` call. :param pipeline_runner: A function that should process user request and return context; - usually it's a :py:meth:`~chatsky.pipeline.pipeline.pipeline.Pipeline._run_pipeline` function. + usually it's a :py:meth:`~chatsky.core.pipeline.Pipeline._run_pipeline` function. :param \\**kwargs: argument, added for compatibility with super class, it shouldn't be used normally. """ self._ctx_id = uuid4() diff --git a/chatsky/messengers/telegram/abstract.py b/chatsky/messengers/telegram/abstract.py index 30742579d..1d464a4a7 100644 --- a/chatsky/messengers/telegram/abstract.py +++ b/chatsky/messengers/telegram/abstract.py @@ -11,8 +11,8 @@ from chatsky.utils.devel.extra_field_helpers import grab_extra_fields from chatsky.messengers.common import MessengerInterfaceWithAttachments -from chatsky.pipeline.types import PipelineRunnerFunction -from chatsky.script.core.message import ( +from chatsky.core.service.types import PipelineRunnerFunction +from chatsky.core.message import ( Animation, Audio, CallbackQuery, diff --git a/chatsky/messengers/telegram/interface.py b/chatsky/messengers/telegram/interface.py index 5015fbf2f..5b8aaab69 100644 --- a/chatsky/messengers/telegram/interface.py +++ b/chatsky/messengers/telegram/interface.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Any, Optional -from chatsky.pipeline.types import PipelineRunnerFunction +from chatsky.core.service.types import PipelineRunnerFunction from .abstract import _AbstractTelegramInterface diff --git a/chatsky/pipeline/pipeline/__init__.py b/chatsky/pipeline/pipeline/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/pipeline/pipeline/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/chatsky/pipeline/pipeline/actor.py b/chatsky/pipeline/pipeline/actor.py deleted file mode 100644 index 20800d6b2..000000000 --- a/chatsky/pipeline/pipeline/actor.py +++ /dev/null @@ -1,411 +0,0 @@ -""" -Actor ------ -Actor is a component of :py:class:`.Pipeline`, that contains the :py:class:`.Script` and handles it. -It is responsible for processing user input and determining the appropriate response based -on the current state of the conversation and the script. -The actor receives requests in the form of a :py:class:`.Context` class, which contains -information about the user's input, the current state of the conversation, and other relevant data. - -The actor uses the dialog graph, represented by the :py:class:`.Script` class, -to determine the appropriate response. The script contains the structure of the conversation, -including the different `nodes` and `transitions`. -It defines the possible paths that the conversation can take, and the conditions that must be met -for a transition to occur. The actor uses this information to navigate the graph -and determine the next step in the conversation. - -Overall, the actor acts as a bridge between the user's input and the dialog graph, -making sure that the conversation follows the expected flow and providing a personalized experience to the user. - -Below you can see a diagram of user request processing with Actor. -Both `request` and `response` are saved to :py:class:`.Context`. - -.. figure:: /_static/drawio/core/user_actor.png -""" - -from __future__ import annotations -import logging -import asyncio -from typing import Union, Callable, Optional, Dict, List, TYPE_CHECKING -from pydantic import Field, model_validator -import copy - -from chatsky.pipeline.pipeline.component import PipelineComponent -from chatsky.utils.turn_caching import cache_clear -from chatsky.script.core.types import ActorStage, NodeLabel2Type, NodeLabel3Type, LabelType -from chatsky.script.core.message import Message - -from chatsky.script.core.context import Context -from chatsky.script.core.script import Script, Node -from chatsky.script.core.normalization import normalize_label, normalize_response -from chatsky.script.core.keywords import GLOBAL, LOCAL -from chatsky.utils.devel.async_helpers import wrap_sync_function_in_async - -logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline - - -# Had to define this earlier, because when Pydantic starts it's __init__ it thinks of this function as -# being referenced before assignment -async def default_condition_handler( - condition: Callable, ctx: Context, pipeline: Pipeline -) -> Callable[[Context, Pipeline], bool]: - """ - The simplest and quickest condition handler for trivial condition handling returns the callable condition: - - :param condition: Condition to copy. - :param ctx: Context of current condition. - :param pipeline: Pipeline we use in this condition. - """ - return await wrap_sync_function_in_async(condition, ctx, pipeline) - - -class Actor(PipelineComponent): - """ - The class which is used to process :py:class:`~chatsky.script.Context` - according to the :py:class:`~chatsky.script.Script`. - """ - - script: Union[Script, Dict] - """ - The dialog scenario: a graph described by the :py:class:`.Keywords`. - While the graph is being initialized, it is validated and then used for the dialog. - """ - start_label: NodeLabel2Type - """ - The start node of :py:class:`~chatsky.script.Script`. The execution begins with it. - """ - fallback_label: Optional[NodeLabel2Type] = None - """ - The label of :py:class:`~chatsky.script.Script`. - Dialog comes into that label if all other transitions failed, - or there was an error while executing the scenario. Defaults to `None`. - """ - label_priority: float = 1.0 - """ - Default priority value for all :py:const:`labels ` - where there is no priority. Defaults to `1.0`. - """ - condition_handler: Callable = Field(default=default_condition_handler) - """ - Handler that processes a call of condition functions. Defaults to `None`. - """ - handlers: Dict[ActorStage, List[Callable]] = Field(default_factory=dict) - """ - This variable is responsible for the usage of external handlers on - the certain stages of work of :py:class:`~chatsky.script.Actor`. - - - key (:py:class:`~chatsky.script.ActorStage`) - Stage in which the handler is called. - - value (`List[Callable]`) - The list of called handlers for each stage. Defaults to an empty `dict`. - - """ - # NB! The following API is highly experimental and may be removed at ANY time WITHOUT FURTHER NOTICE!! - _clean_turn_cache: bool = True - - @model_validator(mode="after") - def __tick_async_flag__(self): - self.calculated_async_flag = False - return self - - @model_validator(mode="after") - def __start_label_validator__(self): - """ - Validate :py:data:`~.Actor.start_label`. - - :raises ValueError: If `start_label` doesn't exist in the given :py:class:`~.Script`. - """ - if not isinstance(self.script, Script): - self.script = Script(script=self.script) - self.start_label = normalize_label(self.start_label) - if self.script.get(self.start_label[0], {}).get(self.start_label[1]) is None: - raise ValueError(f"Unknown start_label={self.start_label}") - return self - - @model_validator(mode="after") - def __fallback_label_validator__(self): - """ - Validate :py:data:`~.Actor.fallback_label`. - :raises ValueError: If `fallback_label` doesn't exist in the given :py:class:`~.Script`. - """ - if self.fallback_label is None: - self.fallback_label = self.start_label - else: - self.fallback_label = normalize_label(self.fallback_label) - if self.script.get(self.fallback_label[0], {}).get(self.fallback_label[1]) is None: - raise ValueError(f"Unknown fallback_label={self.fallback_label}") - return self - - @property - def computed_name(self) -> str: - return "actor" - - async def run_component(self, ctx: Context, pipeline: Pipeline) -> None: - await self._run_handlers(ctx, pipeline, ActorStage.CONTEXT_INIT) - - # get previous node - self._get_previous_node(ctx) - await self._run_handlers(ctx, pipeline, ActorStage.GET_PREVIOUS_NODE) - - # rewrite previous node - self._rewrite_previous_node(ctx) - await self._run_handlers(ctx, pipeline, ActorStage.REWRITE_PREVIOUS_NODE) - - # run pre transitions processing - await self._run_pre_transitions_processing(ctx, pipeline) - await self._run_handlers(ctx, pipeline, ActorStage.RUN_PRE_TRANSITIONS_PROCESSING) - - # get true labels for scopes (GLOBAL, LOCAL, NODE) - await self._get_true_labels(ctx, pipeline) - await self._run_handlers(ctx, pipeline, ActorStage.GET_TRUE_LABELS) - - # get next node - self._get_next_node(ctx) - await self._run_handlers(ctx, pipeline, ActorStage.GET_NEXT_NODE) - - ctx.add_label(ctx.framework_data.actor_data["next_label"][:2]) - - # rewrite next node - self._rewrite_next_node(ctx) - await self._run_handlers(ctx, pipeline, ActorStage.REWRITE_NEXT_NODE) - - # run pre response processing - await self._run_pre_response_processing(ctx, pipeline) - await self._run_handlers(ctx, pipeline, ActorStage.RUN_PRE_RESPONSE_PROCESSING) - - # create response - ctx.framework_data.actor_data["response"] = await self.run_response( - ctx.framework_data.actor_data["pre_response_processed_node"].response, ctx, pipeline - ) - await self._run_handlers(ctx, pipeline, ActorStage.CREATE_RESPONSE) - ctx.add_response(ctx.framework_data.actor_data["response"]) - - await self._run_handlers(ctx, pipeline, ActorStage.FINISH_TURN) - if self._clean_turn_cache: - cache_clear() - - ctx.framework_data.actor_data.clear() - - def _get_previous_node(self, ctx: Context): - ctx.framework_data.actor_data["previous_label"] = ( - normalize_label(ctx.last_label) if ctx.last_label else self.start_label - ) - ctx.framework_data.actor_data["previous_node"] = self.script.get( - ctx.framework_data.actor_data["previous_label"][0], {} - ).get(ctx.framework_data.actor_data["previous_label"][1], Node()) - - async def _get_true_labels(self, ctx: Context, pipeline: Pipeline): - # GLOBAL - ctx.framework_data.actor_data["global_transitions"] = ( - self.script.get(GLOBAL, {}).get(GLOBAL, Node()).transitions - ) - ctx.framework_data.actor_data["global_true_label"] = await self._get_true_label( - ctx.framework_data.actor_data["global_transitions"], ctx, pipeline, GLOBAL, "global" - ) - - # LOCAL - ctx.framework_data.actor_data["local_transitions"] = ( - self.script.get(ctx.framework_data.actor_data["previous_label"][0], {}).get(LOCAL, Node()).transitions - ) - ctx.framework_data.actor_data["local_true_label"] = await self._get_true_label( - ctx.framework_data.actor_data["local_transitions"], - ctx, - pipeline, - ctx.framework_data.actor_data["previous_label"][0], - "local", - ) - - # NODE - ctx.framework_data.actor_data["node_transitions"] = ctx.framework_data.actor_data[ - "pre_transitions_processed_node" - ].transitions - ctx.framework_data.actor_data["node_true_label"] = await self._get_true_label( - ctx.framework_data.actor_data["node_transitions"], - ctx, - pipeline, - ctx.framework_data.actor_data["previous_label"][0], - "node", - ) - - def _get_next_node(self, ctx: Context): - # choose next label - ctx.framework_data.actor_data["next_label"] = self._choose_label( - ctx.framework_data.actor_data["node_true_label"], ctx.framework_data.actor_data["local_true_label"] - ) - ctx.framework_data.actor_data["next_label"] = self._choose_label( - ctx.framework_data.actor_data["next_label"], ctx.framework_data.actor_data["global_true_label"] - ) - # get next node - ctx.framework_data.actor_data["next_node"] = self.script.get( - ctx.framework_data.actor_data["next_label"][0], {} - ).get(ctx.framework_data.actor_data["next_label"][1]) - - def _rewrite_previous_node(self, ctx: Context): - node = ctx.framework_data.actor_data["previous_node"] - flow_label = ctx.framework_data.actor_data["previous_label"][0] - ctx.framework_data.actor_data["previous_node"] = self._overwrite_node( - node, - flow_label, - only_current_node_transitions=True, - ) - - def _rewrite_next_node(self, ctx: Context): - node = ctx.framework_data.actor_data["next_node"] - flow_label = ctx.framework_data.actor_data["next_label"][0] - ctx.framework_data.actor_data["next_node"] = self._overwrite_node(node, flow_label) - - def _overwrite_node( - self, - current_node: Node, - flow_label: LabelType, - only_current_node_transitions: bool = False, - ) -> Node: - overwritten_node = copy.deepcopy(self.script.get(GLOBAL, {}).get(GLOBAL, Node())) - local_node = self.script.get(flow_label, {}).get(LOCAL, Node()) - for node in [local_node, current_node]: - overwritten_node.pre_transitions_processing.update(node.pre_transitions_processing) - overwritten_node.pre_response_processing.update(node.pre_response_processing) - overwritten_node.response = overwritten_node.response if node.response is None else node.response - overwritten_node.misc.update(node.misc) - if not only_current_node_transitions: - overwritten_node.transitions.update(node.transitions) - if only_current_node_transitions: - overwritten_node.transitions = current_node.transitions - return overwritten_node - - async def run_response( - self, - response: Optional[Union[Message, Callable[..., Message]]], - ctx: Context, - pipeline: Pipeline, - ) -> Message: - """ - Executes the normalized response as an asynchronous function. - See the details in the :py:func:`~normalize_response` function of `normalization.py`. - """ - response = normalize_response(response) - return await wrap_sync_function_in_async(response, ctx, pipeline) - - async def _run_processing_parallel(self, processing: dict, ctx: Context, pipeline: Pipeline) -> None: - """ - Execute the processing functions for a particular node simultaneously, - independent of the order. - - Picked depending on the value of the :py:class:`.Pipeline`'s `parallelize_processing` flag. - """ - results = await asyncio.gather( - *[wrap_sync_function_in_async(func, ctx, pipeline) for func in processing.values()], - return_exceptions=True, - ) - for exc, (processing_name, processing_func) in zip(results, processing.items()): - if isinstance(exc, Exception): - logger.error( - f"Exception {exc} for processing_name={processing_name} and processing_func={processing_func}", - exc_info=exc, - ) - - async def _run_processing_sequential(self, processing: dict, ctx: Context, pipeline: Pipeline) -> None: - """ - Execute the processing functions for a particular node in-order. - - Picked depending on the value of the :py:class:`.Pipeline`'s `parallelize_processing` flag. - """ - for processing_name, processing_func in processing.items(): - try: - await wrap_sync_function_in_async(processing_func, ctx, pipeline) - except Exception as exc: - logger.error( - f"Exception {exc} for processing_name={processing_name} and processing_func={processing_func}", - exc_info=exc, - ) - - async def _run_pre_transitions_processing(self, ctx: Context, pipeline: Pipeline) -> None: - """ - Run `PRE_TRANSITIONS_PROCESSING` functions for a particular node. - Pre-transition processing functions can modify the context state - before the direction of the next transition is determined depending on that state. - - The execution order depends on the value of the :py:class:`.Pipeline`'s - `parallelize_processing` flag. - """ - ctx.framework_data.actor_data["processed_node"] = copy.deepcopy(ctx.framework_data.actor_data["previous_node"]) - pre_transitions_processing = ctx.framework_data.actor_data["previous_node"].pre_transitions_processing - - if pipeline.parallelize_processing: - await self._run_processing_parallel(pre_transitions_processing, ctx, pipeline) - else: - await self._run_processing_sequential(pre_transitions_processing, ctx, pipeline) - - ctx.framework_data.actor_data["pre_transitions_processed_node"] = ctx.framework_data.actor_data[ - "processed_node" - ] - del ctx.framework_data.actor_data["processed_node"] - - async def _run_pre_response_processing(self, ctx: Context, pipeline: Pipeline) -> None: - """ - Run `PRE_RESPONSE_PROCESSING` functions for a particular node. - Pre-response processing functions can modify the response before it is - returned to the user. - - The execution order depends on the value of the :py:class:`.Pipeline`'s - `parallelize_processing` flag. - """ - ctx.framework_data.actor_data["processed_node"] = copy.deepcopy(ctx.framework_data.actor_data["next_node"]) - pre_response_processing = ctx.framework_data.actor_data["next_node"].pre_response_processing - - if pipeline.parallelize_processing: - await self._run_processing_parallel(pre_response_processing, ctx, pipeline) - else: - await self._run_processing_sequential(pre_response_processing, ctx, pipeline) - - ctx.framework_data.actor_data["pre_response_processed_node"] = ctx.framework_data.actor_data["processed_node"] - del ctx.framework_data.actor_data["processed_node"] - - async def _get_true_label( - self, - transitions: dict, - ctx: Context, - pipeline: Pipeline, - flow_label: LabelType, - transition_info: str = "", - ) -> Optional[NodeLabel3Type]: - true_labels = [] - - cond_booleans = await asyncio.gather( - *(self.condition_handler(condition, ctx, pipeline) for condition in transitions.values()) - ) - for label, cond_is_true in zip(transitions.keys(), cond_booleans): - if cond_is_true: - if callable(label): - label = await wrap_sync_function_in_async(label, ctx, pipeline) - # TODO: explicit handling of errors - if label is None: - continue - true_labels += [label] - true_labels = [ - ((label[0] if label[0] else flow_label),) - + label[1:2] - + ((self.label_priority if label[2] == float("-inf") else label[2]),) - for label in true_labels - ] - true_labels.sort(key=lambda label: -label[2]) - true_label = true_labels[0] if true_labels else None - logger.debug(f"{transition_info} transitions sorted by priority = {true_labels}") - return true_label - - async def _run_handlers(self, ctx, pipeline: Pipeline, actor_stage: ActorStage): - stage_handlers = self.handlers.get(actor_stage, []) - async_handlers = [wrap_sync_function_in_async(handler, ctx, pipeline) for handler in stage_handlers] - await asyncio.gather(*async_handlers) - - def _choose_label( - self, specific_label: Optional[NodeLabel3Type], general_label: Optional[NodeLabel3Type] - ) -> NodeLabel3Type: - if all([specific_label, general_label]): - chosen_label = specific_label if specific_label[2] >= general_label[2] else general_label - elif any([specific_label, general_label]): - chosen_label = specific_label if specific_label else general_label - else: - chosen_label = self.fallback_label - return chosen_label diff --git a/chatsky/pipeline/pipeline/pipeline.py b/chatsky/pipeline/pipeline/pipeline.py deleted file mode 100644 index 036e0e286..000000000 --- a/chatsky/pipeline/pipeline/pipeline.py +++ /dev/null @@ -1,286 +0,0 @@ -""" -Pipeline --------- -The Pipeline module contains the :py:class:`.Pipeline` class, -which is a fundamental element of Chatsky. The Pipeline class is responsible -for managing and executing the various components (:py:class:`.PipelineComponent`)which make up -the processing of messages from and to users. -It provides a way to organize and structure the messages processing flow. -The Pipeline class is designed to be highly customizable and configurable, -allowing developers to add, remove, or modify the components that make up the messages processing flow. - -The Pipeline class is designed to be used in conjunction with the :py:class:`.PipelineComponent` -class, which is defined in the Component module. Together, these classes provide a powerful and flexible way -to structure and manage the messages processing flow. -""" - -import asyncio -import logging -from functools import cached_property -from typing import Union, List, Dict, Optional, Hashable, Callable -from pydantic import BaseModel, Field, model_validator, computed_field - -from chatsky.context_storages import DBContextStorage -from chatsky.script import Script, Context, ActorStage -from chatsky.script import NodeLabel2Type, Message -from chatsky.utils.turn_caching import cache_clear - -from chatsky.messengers.console import CLIMessengerInterface -from chatsky.messengers.common import MessengerInterface -from chatsky.slots.slots import GroupSlot -from chatsky.pipeline.service.group import ServiceGroup -from chatsky.pipeline.service.extra import ComponentExtraHandler -from ..types import ( - GlobalExtraHandlerType, - ExtraHandlerFunction, -) -from .utils import finalize_service_group -from chatsky.pipeline.pipeline.actor import Actor, default_condition_handler - -logger = logging.getLogger(__name__) - - -class Pipeline(BaseModel, extra="forbid", arbitrary_types_allowed=True): - """ - Class that automates service execution and creates service pipeline. - It accepts constructor parameters: - """ - - pre_services: ServiceGroup = Field(default_factory=list) - """ - List of :py:class:`~.Service` or :py:class:`~.ServiceGroup` - that will be executed before Actor. - """ - post_services: ServiceGroup = Field(default_factory=list) - """ - List of :py:class:`~.Service` or :py:class:`~.ServiceGroup` that will be - executed after :py:class:`~.Actor`. It constructs root - service group by merging `pre_services` + actor + `post_services`. It will always be named pipeline. - """ - script: Union[Script, Dict] - """ - (required) A :py:class:`~.Script` instance (object or dict). - """ - start_label: NodeLabel2Type - """ - (required) :py:class:`~.Actor` start label. - """ - fallback_label: Optional[NodeLabel2Type] = None - """ - :py:class:`~.Actor` fallback label. - """ - label_priority: float = 1.0 - """ - Default priority value for all actor :py:const:`labels ` - where there is no priority. Defaults to `1.0`. - """ - condition_handler: Callable = Field(default=default_condition_handler) - """ - Handler that processes a call of actor condition functions. Defaults to `None`. - """ - slots: GroupSlot = Field(default_factory=GroupSlot) - """ - Slots configuration. - """ - handlers: Dict[ActorStage, List[Callable]] = Field(default_factory=dict) - """ - This variable is responsible for the usage of external handlers on - the certain stages of work of :py:class:`~chatsky.script.Actor`. - - - key: :py:class:`~chatsky.script.ActorStage` - Stage in which the handler is called. - - value: List[Callable] - The list of called handlers for each stage. Defaults to an empty `dict`. - - """ - messenger_interface: MessengerInterface = Field(default_factory=CLIMessengerInterface) - """ - An `AbsMessagingInterface` instance for this pipeline. - """ - context_storage: Union[DBContextStorage, Dict] = Field(default_factory=dict) - """ - A :py:class:`~.DBContextStorage` instance for this pipeline or - a dict to store dialog :py:class:`~.Context`. - """ - before_handler: ComponentExtraHandler = Field(default_factory=list) - """ - List of :py:class:`~._ComponentExtraHandler` to add to the group. - """ - after_handler: ComponentExtraHandler = Field(default_factory=list) - """ - List of :py:class:`~._ComponentExtraHandler` to add to the group. - """ - timeout: Optional[float] = None - """ - Timeout to add to pipeline root service group. - """ - optimization_warnings: bool = False - """ - Asynchronous pipeline optimization check request flag; - warnings will be sent to logs. Additionally, it has some calculated fields: - - - `_services_pipeline` is a pipeline root :py:class:`~.ServiceGroup` object, - - `actor` is a pipeline actor, found among services. - - """ - parallelize_processing: bool = False - """ - This flag determines whether or not the functions - defined in the ``PRE_RESPONSE_PROCESSING`` and ``PRE_TRANSITIONS_PROCESSING`` sections - of the script should be parallelized over respective groups. - """ - _clean_turn_cache: Optional[bool] - - @computed_field - @cached_property - def actor(self) -> Actor: - return Actor( - script=self.script, - start_label=self.start_label, - fallback_label=self.fallback_label, - label_priority=self.label_priority, - condition_handler=self.condition_handler, - handlers=self.handlers, - ) - - @computed_field - @cached_property - def _services_pipeline(self) -> ServiceGroup: - components = [self.pre_services, self.actor, self.post_services] - services_pipeline = ServiceGroup( - components=components, - before_handler=self.before_handler, - after_handler=self.after_handler, - timeout=self.timeout, - ) - services_pipeline.name = "pipeline" - services_pipeline.path = ".pipeline" - return services_pipeline - - @model_validator(mode="after") - def __pipeline_init__(self): - finalize_service_group(self._services_pipeline, path=self._services_pipeline.path) - - if self.optimization_warnings: - self._services_pipeline.log_optimization_warnings() - - # NB! The following API is highly experimental and may be removed at ANY time WITHOUT FURTHER NOTICE!! - self._clean_turn_cache = True - if self._clean_turn_cache: - self.actor._clean_turn_cache = False - return self - - def add_global_handler( - self, - global_handler_type: GlobalExtraHandlerType, - extra_handler: ExtraHandlerFunction, - whitelist: Optional[List[str]] = None, - blacklist: Optional[List[str]] = None, - ): - """ - Method for adding global wrappers to pipeline. - Different types of global wrappers are called before/after pipeline execution - or before/after each pipeline component. - They can be used for pipeline statistics collection or other functionality extensions. - NB! Global wrappers are still wrappers, - they shouldn't be used for much time-consuming tasks (see ../service/wrapper.py). - - :param global_handler_type: (required) indication where the wrapper - function should be executed. - :param extra_handler: (required) wrapper function itself. - :type extra_handler: ExtraHandlerFunction - :param whitelist: a list of services to only add this wrapper to. - :param blacklist: a list of services to not add this wrapper to. - :return: `None` - """ - - def condition(name: str) -> bool: - return (whitelist is None or name in whitelist) and (blacklist is None or name not in blacklist) - - if ( - global_handler_type is GlobalExtraHandlerType.BEFORE_ALL - or global_handler_type is GlobalExtraHandlerType.AFTER_ALL - ): - whitelist = ["pipeline"] - global_handler_type = ( - GlobalExtraHandlerType.BEFORE - if global_handler_type is GlobalExtraHandlerType.BEFORE_ALL - else GlobalExtraHandlerType.AFTER - ) - - self._services_pipeline.add_extra_handler(global_handler_type, extra_handler, condition) - - @property - def info_dict(self) -> dict: - """ - Property for retrieving info dictionary about this pipeline. - Returns info dict, containing most important component public fields as well as its type. - All complex or unserializable fields here are replaced with 'Instance of [type]'. - """ - return { - "type": type(self).__name__, - "messenger_interface": f"Instance of {type(self.messenger_interface).__name__}", - "context_storage": f"Instance of {type(self.context_storage).__name__}", - "services": [self._services_pipeline.info_dict], - } - - async def _run_pipeline( - self, request: Message, ctx_id: Optional[Hashable] = None, update_ctx_misc: Optional[dict] = None - ) -> Context: - """ - Method that should be invoked on user input. - This method has the same signature as :py:class:`~chatsky.pipeline.types.PipelineRunnerFunction`. - """ - if ctx_id is None: - ctx = Context() - elif isinstance(self.context_storage, DBContextStorage): - ctx = await self.context_storage.get_async(ctx_id, Context(id=ctx_id)) - else: - ctx = self.context_storage.get(ctx_id, Context(id=ctx_id)) - - if update_ctx_misc is not None: - ctx.misc.update(update_ctx_misc) - - if self.slots is not None: - ctx.framework_data.slot_manager.set_root_slot(self.slots) - - ctx.add_request(request) - result = await self._services_pipeline(ctx, self) - - if asyncio.iscoroutine(result): - await result - - ctx.framework_data.service_states.clear() - - if isinstance(self.context_storage, DBContextStorage): - await self.context_storage.set_item_async(ctx_id, ctx) - else: - self.context_storage[ctx_id] = ctx - if self._clean_turn_cache: - cache_clear() - - return ctx - - def run(self): - """ - Method that starts a pipeline and connects to `messenger_interface`. - It passes `_run_pipeline` to `messenger_interface` as a callbacks, - so every time user request is received, `_run_pipeline` will be called. - This method can be both blocking and non-blocking. It depends on current `messenger_interface` nature. - Message interfaces that run in a loop block current thread. - """ - asyncio.run(self.messenger_interface.connect(self._run_pipeline)) - - def __call__( - self, request: Message, ctx_id: Optional[Hashable] = None, update_ctx_misc: Optional[dict] = None - ) -> Context: - """ - Method that executes pipeline once. - Basically, it is a shortcut for `_run_pipeline`. - NB! When pipeline is executed this way, `messenger_interface` won't be initiated nor connected. - - This method has the same signature as :py:class:`~chatsky.pipeline.types.PipelineRunnerFunction`. - """ - return asyncio.run(self._run_pipeline(request, ctx_id, update_ctx_misc)) - - @property - def script(self) -> Script: - return self.actor.script diff --git a/chatsky/pipeline/service/__init__.py b/chatsky/pipeline/service/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/pipeline/service/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/chatsky/processing/__init__.py b/chatsky/processing/__init__.py new file mode 100644 index 000000000..4bc71cf5d --- /dev/null +++ b/chatsky/processing/__init__.py @@ -0,0 +1,2 @@ +from .standard import ModifyResponse +from .slots import Extract, ExtractAll, Unset, UnsetAll, FillTemplate diff --git a/chatsky/processing/slots.py b/chatsky/processing/slots.py new file mode 100644 index 000000000..6bf017782 --- /dev/null +++ b/chatsky/processing/slots.py @@ -0,0 +1,95 @@ +""" +Slot Processing +--------------- +This module provides wrappers for :py:class:`~chatsky.slots.slots.SlotManager`'s API as :py:class:`.BaseProcessing` +subclasses. +""" + +import asyncio +import logging +from typing import List + +from chatsky.slots.slots import SlotName +from chatsky.core import Context, BaseProcessing +from chatsky.responses.slots import FilledTemplate + +logger = logging.getLogger(__name__) + + +class Extract(BaseProcessing): + """ + Extract slots listed slots. + This will override all slots even if they are already extracted. + """ + + slots: List[SlotName] + """A list of slot names to extract.""" + + def __init__(self, *slots: SlotName): + super().__init__(slots=slots) + + async def call(self, ctx: Context): + manager = ctx.framework_data.slot_manager + results = await asyncio.gather( + *(manager.extract_slot(slot, ctx) for slot in self.slots), return_exceptions=True + ) + + for result in results: + if isinstance(result, Exception): + logger.exception("An exception occurred during slot extraction.", exc_info=result) + + +class ExtractAll(BaseProcessing): + """ + Extract all slots defined in the pipeline. + """ + + async def call(self, ctx: Context): + manager = ctx.framework_data.slot_manager + await manager.extract_all(ctx) + + +class Unset(BaseProcessing): + """ + Mark specified slots as not extracted and clear extracted values. + """ + + slots: List[SlotName] + """A list of slot names to extract.""" + + def __init__(self, *slots: SlotName): + super().__init__(slots=slots) + + async def call(self, ctx: Context): + manager = ctx.framework_data.slot_manager + for slot in self.slots: + try: + manager.unset_slot(slot) + except Exception as exc: + logger.exception("An exception occurred during slot resetting.", exc_info=exc) + + +class UnsetAll(BaseProcessing): + """ + Mark all slots as not extracted and clear all extracted values. + """ + + async def call(self, ctx: Context): + manager = ctx.framework_data.slot_manager + manager.unset_all_slots() + + +class FillTemplate(BaseProcessing): + """ + Fill the response template in the current node. + + Response message of the current node should be a format-string: e.g. ``"Your username is {profile.username}"``. + """ + + async def call(self, ctx: Context): + response = ctx.current_node.response + + if response is None: + return + + ctx.current_node.response = FilledTemplate(template=response) diff --git a/chatsky/processing/standard.py b/chatsky/processing/standard.py new file mode 100644 index 000000000..8b3fa2aab --- /dev/null +++ b/chatsky/processing/standard.py @@ -0,0 +1,41 @@ +""" +Standard Processing +------------------- +This module provides basic processing functions. + +- :py:class:`ModifyResponse` modifies response of the :py:attr:`.Context.current_node`. +""" + +import abc + +from chatsky.core import BaseProcessing, BaseResponse, Context, MessageInitTypes + + +class ModifyResponse(BaseProcessing, abc.ABC): + """ + Modify the response function of the :py:attr:`.Context.current_node` to call + :py:meth:`modified_response` instead. + """ + + @abc.abstractmethod + async def modified_response(self, original_response: BaseResponse, ctx: Context) -> MessageInitTypes: + """ + A function that replaces response of the current node. + + :param original_response: Response of the current node when :py:class:`.ModifyResponse` is called. + :param ctx: Current context. + """ + raise NotImplementedError + + async def call(self, ctx: Context) -> None: + current_response = ctx.current_node.response + if current_response is None: + return + + processing_object = self + + class ModifiedResponse(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + return await processing_object.modified_response(current_response, ctx) + + ctx.current_node.response = ModifiedResponse() diff --git a/chatsky/responses/__init__.py b/chatsky/responses/__init__.py new file mode 100644 index 000000000..06ca4b2f7 --- /dev/null +++ b/chatsky/responses/__init__.py @@ -0,0 +1,2 @@ +from .standard import RandomChoice +from .slots import FilledTemplate diff --git a/chatsky/responses/slots.py b/chatsky/responses/slots.py new file mode 100644 index 000000000..910b59892 --- /dev/null +++ b/chatsky/responses/slots.py @@ -0,0 +1,61 @@ +""" +Slot Responses +-------------- +Slot-related responses. +""" + +from typing import Union, Literal +import logging + +from chatsky.core import Context, Message, BaseResponse +from chatsky.core.script_function import AnyResponse +from chatsky.core.message import MessageInitTypes + + +logger = logging.getLogger(__name__) + + +class FilledTemplate(BaseResponse): + """ + Fill template with slot values. + The `text` attribute of the template message should be a format-string: + e.g. "Your username is {profile.username}". + + For the example above, if ``profile.username`` slot has value "admin", + it would return a copy of the message with the following text: + "Your username is admin". + """ + + template: AnyResponse + """A response to use as a template.""" + on_exception: Literal["keep_template", "return_none"] = "return_none" + """ + What to do if template filling fails. + + - "keep_template": :py:attr:`template` is returned, unfilled. + - "return_none": an empty message is returned. + """ + + def __init__( + self, + template: Union[MessageInitTypes, BaseResponse], + on_exception: Literal["keep_template", "return_none"] = "return_none", + ): + super().__init__(template=template, on_exception=on_exception) + + async def call(self, ctx: Context) -> MessageInitTypes: + result = await self.template(ctx) + + if result.text is not None: + filled = ctx.framework_data.slot_manager.fill_template(result.text) + if isinstance(filled, str): + result.text = filled + return result + else: + if self.on_exception == "return_none": + return Message() + else: + return result + else: + logger.warning(f"`template` of `FilledTemplate` returned `Message` without `text`: {result}.") + return result diff --git a/chatsky/responses/standard.py b/chatsky/responses/standard.py new file mode 100644 index 000000000..79924d8c3 --- /dev/null +++ b/chatsky/responses/standard.py @@ -0,0 +1,26 @@ +""" +Standard Responses +------------------ +This module provides basic responses. +""" + +import random +from typing import List + +from chatsky.core import BaseResponse, Message, Context +from chatsky.core.message import MessageInitTypes + + +class RandomChoice(BaseResponse): + """ + Return a random message from :py:attr:`responses`. + """ + + responses: List[Message] + """A list of messages to choose from.""" + + def __init__(self, *responses: MessageInitTypes): + super().__init__(responses=responses) + + async def call(self, ctx: Context) -> MessageInitTypes: + return random.choice(self.responses) diff --git a/chatsky/script/__init__.py b/chatsky/script/__init__.py deleted file mode 100644 index 942d9441d..000000000 --- a/chatsky/script/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- - -from .core.context import Context -from .core.keywords import ( - Keywords, - GLOBAL, - LOCAL, - TRANSITIONS, - RESPONSE, - MISC, - PRE_RESPONSE_PROCESSING, - PRE_TRANSITIONS_PROCESSING, -) -from .core.script import Node, Script -from .core.types import ( - LabelType, - NodeLabel1Type, - NodeLabel2Type, - NodeLabel3Type, - NodeLabelTupledType, - ConstLabel, - Label, - ConditionType, - ActorStage, -) -from .core.message import Message diff --git a/chatsky/script/conditions/__init__.py b/chatsky/script/conditions/__init__.py deleted file mode 100644 index 9b5fe812f..000000000 --- a/chatsky/script/conditions/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- - -from .std_conditions import ( - exact_match, - has_text, - regexp, - check_cond_seq, - aggregate, - any, - all, - negation, - has_last_labels, - true, - false, - agg, - neg, - has_callback_query, -) diff --git a/chatsky/script/conditions/std_conditions.py b/chatsky/script/conditions/std_conditions.py deleted file mode 100644 index 7a5479f9a..000000000 --- a/chatsky/script/conditions/std_conditions.py +++ /dev/null @@ -1,268 +0,0 @@ -""" -Conditions ----------- -Conditions are one of the most important components of the dialog graph. -They determine the possibility of transition from one node of the graph to another. -The conditions are used to specify when a particular transition should occur, based on certain criteria. -This module contains a standard set of scripting conditions that can be used to control the flow of a conversation. -These conditions can be used to check the current context, the user's input, -or other factors that may affect the conversation flow. -""" - -from typing import Callable, Pattern, Union, List, Optional -import logging -import re - -from pydantic import validate_call - -from chatsky.pipeline import Pipeline -from chatsky.script import NodeLabel2Type, Context, Message -from chatsky.script.core.message import CallbackQuery - -logger = logging.getLogger(__name__) - - -@validate_call -def exact_match(match: Union[str, Message], skip_none: bool = True) -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler returns `True` only if the last user phrase - is the same `Message` as the `match`. - If `skip_none` the handler will not compare `None` fields of `match`. - - :param match: A `Message` variable to compare user request with. - Can also accept `str`, which will be converted into a `Message` with its text field equal to `match`. - :param skip_none: Whether fields should be compared if they are `None` in :py:const:`match`. - """ - - def exact_match_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - request = ctx.last_request - nonlocal match - if isinstance(match, str): - match = Message(text=match) - if request is None: - return False - for field in match.model_fields: - match_value = match.__getattribute__(field) - if skip_none and match_value is None: - continue - if field in request.model_fields.keys(): - if request.__getattribute__(field) != match.__getattribute__(field): - return False - else: - return False - return True - - return exact_match_condition_handler - - -@validate_call -def has_text(text: str) -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler returns `True` only if the last user phrase - contains the phrase specified in `text`. - - :param text: A `str` variable to look for within the user request. - """ - - def has_text_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - request = ctx.last_request - return text in request.text - - return has_text_condition_handler - - -@validate_call -def regexp(pattern: Union[str, Pattern], flags: Union[int, re.RegexFlag] = 0) -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler returns `True` only if the last user phrase contains - `pattern` with `flags`. - - :param pattern: The `RegExp` pattern. - :param flags: Flags for this pattern. Defaults to 0. - """ - pattern = re.compile(pattern, flags) - - def regexp_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - request = ctx.last_request - if isinstance(request, Message): - if request.text is None: - return False - return bool(pattern.search(request.text)) - else: - logger.error(f"request has to be str type, but got request={request}") - return False - - return regexp_condition_handler - - -@validate_call -def check_cond_seq(cond_seq: list): - """ - Check if the list consists only of Callables. - - :param cond_seq: List of conditions to check. - """ - for cond in cond_seq: - if not callable(cond): - raise TypeError(f"{cond_seq} has to consist of callable objects") - - -_any = any -""" -_any is an alias for any. -""" -_all = all -""" -_all is an alias for all. -""" - - -@validate_call -def aggregate(cond_seq: list, aggregate_func: Callable = _any) -> Callable[[Context, Pipeline], bool]: - """ - Aggregate multiple functions into one by using aggregating function. - - :param cond_seq: List of conditions to check. - :param aggregate_func: Function to aggregate conditions. Defaults to :py:func:`_any`. - """ - check_cond_seq(cond_seq) - - def aggregate_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - try: - return bool(aggregate_func([cond(ctx, pipeline) for cond in cond_seq])) - except Exception as exc: - logger.error(f"Exception {exc} for {cond_seq}, {aggregate_func} and {ctx.last_request}", exc_info=exc) - return False - - return aggregate_condition_handler - - -@validate_call -def any(cond_seq: list) -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler returns `True` - if any function from the list is `True`. - - :param cond_seq: List of conditions to check. - """ - _agg = aggregate(cond_seq, _any) - - def any_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - return _agg(ctx, pipeline) - - return any_condition_handler - - -@validate_call -def all(cond_seq: list) -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler returns `True` only - if all functions from the list are `True`. - - :param cond_seq: List of conditions to check. - """ - _agg = aggregate(cond_seq, _all) - - def all_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - return _agg(ctx, pipeline) - - return all_condition_handler - - -@validate_call -def negation(condition: Callable) -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler returns negation of the :py:func:`~condition`: `False` - if :py:func:`~condition` holds `True` and returns `True` otherwise. - - :param condition: Any :py:func:`~condition`. - """ - - def negation_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - return not condition(ctx, pipeline) - - return negation_condition_handler - - -@validate_call -def has_last_labels( - flow_labels: Optional[List[str]] = None, - labels: Optional[List[NodeLabel2Type]] = None, - last_n_indices: int = 1, -) -> Callable[[Context, Pipeline], bool]: - """ - Return condition handler. This handler returns `True` if any label from - last `last_n_indices` context labels is in - the `flow_labels` list or in - the `labels` list. - - :param flow_labels: List of labels to check. Every label has type `str`. Empty if not set. - :param labels: List of labels corresponding to the nodes. Empty if not set. - :param last_n_indices: Number of last utterances to check. - """ - # todo: rewrite docs & function itself - flow_labels = [] if flow_labels is None else flow_labels - labels = [] if labels is None else labels - - def has_last_labels_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - label = list(ctx.labels.values())[-last_n_indices:] - for label in list(ctx.labels.values())[-last_n_indices:]: - label = label if label else (None, None) - if label[0] in flow_labels or label in labels: - return True - return False - - return has_last_labels_condition_handler - - -@validate_call -def true() -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler always returns `True`. - """ - - def true_handler(ctx: Context, pipeline: Pipeline) -> bool: - return True - - return true_handler - - -@validate_call -def false() -> Callable[[Context, Pipeline], bool]: - """ - Return function handler. This handler always returns `False`. - """ - - def false_handler(ctx: Context, pipeline: Pipeline) -> bool: - return False - - return false_handler - - -# aliases -agg = aggregate -""" -:py:func:`~agg` is an alias for :py:func:`~aggregate`. -""" -neg = negation -""" -:py:func:`~neg` is an alias for :py:func:`~negation`. -""" - - -def has_callback_query(expected_query_string: str) -> Callable[[Context, Pipeline], bool]: - """ - Condition that checks if :py:attr:`~.CallbackQuery.query_string` - of the last message matches `expected_query_string`. - - :param expected_query_string: The expected query string to compare with. - :return: The callback query comparator function. - """ - - def has_callback_query_handler(ctx: Context, _: Pipeline) -> bool: - last_request = ctx.last_request - if last_request is None or last_request.attachments is None: - return False - return CallbackQuery(query_string=expected_query_string) in last_request.attachments - - return has_callback_query_handler diff --git a/chatsky/script/core/__init__.py b/chatsky/script/core/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/script/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/chatsky/script/core/context.py b/chatsky/script/core/context.py deleted file mode 100644 index 30c589a96..000000000 --- a/chatsky/script/core/context.py +++ /dev/null @@ -1,283 +0,0 @@ -""" -Context -------- -A Context is a data structure that is used to store information about the current state of a conversation. -It is used to keep track of the user's input, the current stage of the conversation, and any other -information that is relevant to the current context of a dialog. -The Context provides a convenient interface for working with data, allowing developers to easily add, -retrieve, and manipulate data as the conversation progresses. - -The Context data structure provides several key features to make working with data easier. -Developers can use the context to store any information that is relevant to the current conversation, -such as user data, session data, conversation history, or etc. -This allows developers to easily access and use this data throughout the conversation flow. - -Another important feature of the context is data serialization. -The context can be easily serialized to a format that can be stored or transmitted, such as JSON. -This allows developers to save the context data and resume the conversation later. -""" - -from __future__ import annotations -import logging -from uuid import UUID, uuid4 -from typing import Any, Optional, Union, Dict, List, Set, TYPE_CHECKING - -from pydantic import BaseModel, Field, field_validator - -from chatsky.script.core.message import Message -from chatsky.script.core.types import NodeLabel2Type -from chatsky.pipeline.types import ComponentExecutionState -from chatsky.slots.slots import SlotManager - -if TYPE_CHECKING: - from chatsky.script.core.script import Node - -logger = logging.getLogger(__name__) - - -def get_last_index(dictionary: dict) -> int: - """ - Obtain the last index from the `dictionary`. Return `-1` if the `dict` is empty. - - :param dictionary: Dictionary with unsorted keys. - :return: Last index from the `dictionary`. - """ - indices = list(dictionary) - return indices[-1] if indices else -1 - - -class FrameworkData(BaseModel): - """ - Framework uses this to store data related to any of its modules. - """ - - service_states: Dict[str, ComponentExecutionState] = Field(default_factory=dict, exclude=True) - "Statuses of all the pipeline services. Cleared at the end of every turn." - actor_data: Dict[str, Any] = Field(default_factory=dict, exclude=True) - "Actor service data. Cleared at the end of every turn." - stats: Dict[str, Any] = Field(default_factory=dict) - "Enables complex stats collection across multiple turns." - slot_manager: SlotManager = Field(default_factory=SlotManager) - "Stores extracted slots." - - -class Context(BaseModel): - """ - A structure that is used to store data about the context of a dialog. - - Avoid storing unserializable data in the fields of this class in order for - context storages to work. - """ - - id: Union[UUID, int, str] = Field(default_factory=uuid4) - """ - `id` is the unique context identifier. By default, randomly generated using `uuid4` `id` is used. - `id` can be used to trace the user behavior, e.g while collecting the statistical data. - """ - labels: Dict[int, NodeLabel2Type] = Field(default_factory=dict) - """ - `labels` stores the history of all passed `labels` - - - key - `id` of the turn. - - value - `label` on this turn. - """ - requests: Dict[int, Message] = Field(default_factory=dict) - """ - `requests` stores the history of all `requests` received by the agent - - - key - `id` of the turn. - - value - `request` on this turn. - """ - responses: Dict[int, Message] = Field(default_factory=dict) - """ - `responses` stores the history of all agent `responses` - - - key - `id` of the turn. - - value - `response` on this turn. - """ - misc: Dict[str, Any] = Field(default_factory=dict) - """ - `misc` stores any custom data. The scripting doesn't use this dictionary by default, - so storage of any data won't reflect on the work on the internal Chatsky Scripting functions. - - Avoid storing unserializable data in order for context storages to work. - - - key - Arbitrary data name. - - value - Arbitrary data. - """ - framework_data: FrameworkData = Field(default_factory=FrameworkData) - """ - This attribute is used for storing custom data required for pipeline execution. - It is meant to be used by the framework only. Accessing it may result in pipeline breakage. - """ - - @field_validator("labels", "requests", "responses") - @classmethod - def sort_dict_keys(cls, dictionary: dict) -> dict: - """ - Sort the keys in the `dictionary`. This needs to be done after deserialization, - since the keys are deserialized in a random order. - - :param dictionary: Dictionary with unsorted keys. - :return: Dictionary with sorted keys. - """ - return {key: dictionary[key] for key in sorted(dictionary)} - - @classmethod - def cast(cls, ctx: Optional[Union[Context, dict, str]] = None, *args, **kwargs) -> Context: - """ - Transform different data types to the objects of the - :py:class:`~.Context` class. - Return an object of the :py:class:`~.Context` - type that is initialized by the input data. - - :param ctx: Data that is used to initialize an object of the - :py:class:`~.Context` type. - An empty :py:class:`~.Context` object is returned if no data is given. - :return: Object of the :py:class:`~.Context` - type that is initialized by the input data. - """ - if not ctx: - ctx = Context(*args, **kwargs) - elif isinstance(ctx, dict): - ctx = Context.model_validate(ctx) - elif isinstance(ctx, str): - ctx = Context.model_validate_json(ctx) - elif not isinstance(ctx, Context): - raise ValueError( - f"Context expected to be an instance of the Context class " - f"or an instance of the dict/str(json) type. Got: {type(ctx)}" - ) - return ctx - - def add_request(self, request: Message): - """ - Add a new `request` to the context. - The new `request` is added with the index of `last_index + 1`. - - :param request: `request` to be added to the context. - """ - request_message = Message.model_validate(request) - last_index = get_last_index(self.requests) - self.requests[last_index + 1] = request_message - - def add_response(self, response: Message): - """ - Add a new `response` to the context. - The new `response` is added with the index of `last_index + 1`. - - :param response: `response` to be added to the context. - """ - response_message = Message.model_validate(response) - last_index = get_last_index(self.responses) - self.responses[last_index + 1] = response_message - - def add_label(self, label: NodeLabel2Type): - """ - Add a new :py:data:`~.NodeLabel2Type` to the context. - The new `label` is added with the index of `last_index + 1`. - - :param label: `label` that we need to add to the context. - """ - last_index = get_last_index(self.labels) - self.labels[last_index + 1] = label - - def clear( - self, - hold_last_n_indices: int, - field_names: Union[Set[str], List[str]] = {"requests", "responses", "labels"}, - ): - """ - Delete all records from the `requests`/`responses`/`labels` except for - the last `hold_last_n_indices` turns. - If `field_names` contains `misc` field, `misc` field is fully cleared. - - :param hold_last_n_indices: Number of last turns to keep. - :param field_names: Properties of :py:class:`~.Context` to clear. - Defaults to {"requests", "responses", "labels"} - """ - field_names = field_names if isinstance(field_names, set) else set(field_names) - if "requests" in field_names: - for index in list(self.requests)[:-hold_last_n_indices]: - del self.requests[index] - if "responses" in field_names: - for index in list(self.responses)[:-hold_last_n_indices]: - del self.responses[index] - if "misc" in field_names: - self.misc.clear() - if "labels" in field_names: - for index in list(self.labels)[:-hold_last_n_indices]: - del self.labels[index] - if "framework_data" in field_names: - self.framework_data = FrameworkData() - - @property - def last_label(self) -> Optional[NodeLabel2Type]: - """ - Return the last :py:data:`~.NodeLabel2Type` of - the :py:class:`~.Context`. - Return `None` if `labels` is empty. - - Since `start_label` is not added to the `labels` field, - empty `labels` usually indicates that the current node is the `start_node`. - """ - last_index = get_last_index(self.labels) - return self.labels.get(last_index) - - @property - def last_response(self) -> Optional[Message]: - """ - Return the last `response` of the current :py:class:`~.Context`. - Return `None` if `responses` is empty. - """ - last_index = get_last_index(self.responses) - return self.responses.get(last_index) - - @last_response.setter - def last_response(self, response: Optional[Message]): - """ - Set the last `response` of the current :py:class:`~.Context`. - Required for use with various response wrappers. - """ - last_index = get_last_index(self.responses) - self.responses[last_index] = Message() if response is None else Message.model_validate(response) - - @property - def last_request(self) -> Optional[Message]: - """ - Return the last `request` of the current :py:class:`~.Context`. - Return `None` if `requests` is empty. - """ - last_index = get_last_index(self.requests) - return self.requests.get(last_index) - - @last_request.setter - def last_request(self, request: Optional[Message]): - """ - Set the last `request` of the current :py:class:`~.Context`. - Required for use with various request wrappers. - """ - last_index = get_last_index(self.requests) - self.requests[last_index] = Message() if request is None else Message.model_validate(request) - - @property - def current_node(self) -> Optional[Node]: - """ - Return current :py:class:`~chatsky.script.core.script.Node`. - """ - actor_data = self.framework_data.actor_data - node = ( - actor_data.get("processed_node") - or actor_data.get("pre_response_processed_node") - or actor_data.get("next_node") - or actor_data.get("pre_transitions_processed_node") - or actor_data.get("previous_node") - ) - if node is None: - logger.warning( - "The `current_node` method should be called " - "when an actor is running between the " - "`ActorStage.GET_PREVIOUS_NODE` and `ActorStage.FINISH_TURN` stages." - ) - - return node diff --git a/chatsky/script/core/keywords.py b/chatsky/script/core/keywords.py deleted file mode 100644 index c2ff5baec..000000000 --- a/chatsky/script/core/keywords.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Keywords --------- -Keywords are used to define the dialog graph, which is the structure of a conversation. -They are used to determine all nodes in the script and to assign python objects and python functions for nodes. - -""" - -from enum import Enum - - -class Keywords(str, Enum): - """ - Keywords used to define the dialog script (:py:class:`~chatsky.script.Script`). - The data type `dict` is used to describe the scenario. - `Enums` of this class are used as keys in this `dict`. - Different keys correspond to the different value types aimed at different purposes. - - Enums: - - GLOBAL: Enum(auto) - This keyword is used to define a global node. - The value that corresponds to this key has the `dict` type with keywords: - - `{TRANSITIONS:..., RESPONSE:..., PRE_RESPONSE_PROCESSING:..., MISC:...}`. - There can be only one global node in a script :py:class:`~chatsky.script.Script`. - The global node is defined at the flow level as opposed to regular nodes. - This node allows to define default global values for all nodes. - - LOCAL: Enum(auto) - This keyword is used to define the local node. - The value that corresponds to this key has the `dict` type with keywords: - - `{TRANSITIONS:..., RESPONSE:..., PRE_RESPONSE_PROCESSING:..., MISC:...}`. - The local node is defined in the same way as all other nodes in the flow of this node. - It also allows to redefine default values for all nodes in this node's flow. - - TRANSITIONS: Enum(auto) - This keyword defines possible transitions from node. - The value that corresponds to the `TRANSITIONS` key has the `dict` type. - Every key-value pair describes the transition node and the condition: - - `{label_to_transition_0: condition_for_transition_0, ..., label_to_transition_N: condition_for_transition_N}`, - - where `label_to_transition_i` is a node into which the actor make the transition in case of - `condition_for_transition_i == True`. - - RESPONSE: Enum(auto) - The keyword specifying the result which is returned to the user after getting to the node. - Value corresponding to the `RESPONSE` key can have any data type. - - MISC: Enum(auto) - The keyword specifying `dict` containing extra data, - which were not aimed to be used in the standard functions of `DFE`. - Value corresponding to the `MISC` key must have `dict` type: - - `{"VAR_KEY_0": VAR_VALUE_0, ..., "VAR_KEY_N": VAR_VALUE_N}`, - - where `"VAR_KEY_0"` is an arbitrary name of the value which is saved into the `MISC`. - - PRE_RESPONSE_PROCESSING: Enum(auto) - The keyword specifying the preprocessing that is called before the response generation. - The value that corresponds to the `PRE_RESPONSE_PROCESSING` key must have the `dict` type: - - `{"PRE_RESPONSE_PROC_0": pre_response_proc_func_0, ..., "PRE_RESPONSE_PROC_N": pre_response_proc__func_N}`, - - where `"PRE_RESPONSE_PROC_i"` is an arbitrary name of the preprocessing stage in the pipeline. - Unless the :py:class:`~chatsky.pipeline.pipeline.Pipeline`'s `parallelize_processing` flag - is set to `True`, calls to `pre_response_proc__func_i` are made in-order. - - PRE_TRANSITIONS_PROCESSING: Enum(auto) - The keyword specifying the preprocessing that is called before the transition. - The value that corresponds to the `PRE_TRANSITIONS_PROCESSING` key must have the `dict` type: - - `{"PRE_TRANSITIONS_PROC_0": pre_transitions_proc_func_0, ..., - "PRE_TRANSITIONS_PROC_N": pre_transitions_proc_func_N}`, - - where `"PRE_TRANSITIONS_PROC_i"` is an arbitrary name of the preprocessing stage in the pipeline. - Unless the :py:class:`~chatsky.pipeline.pipeline.Pipeline`'s `parallelize_processing` flag - is set to `True`, calls to `pre_transitions_proc_func_i` are made in-order. - - """ - - GLOBAL = "global" - LOCAL = "local" - TRANSITIONS = "transitions" - RESPONSE = "response" - MISC = "misc" - PRE_RESPONSE_PROCESSING = "pre_response_processing" - PRE_TRANSITIONS_PROCESSING = "pre_transitions_processing" - PROCESSING = "pre_transitions_processing" - - -# Redefine shortcuts -GLOBAL = Keywords.GLOBAL -LOCAL = Keywords.LOCAL -TRANSITIONS = Keywords.TRANSITIONS -RESPONSE = Keywords.RESPONSE -MISC = Keywords.MISC -PRE_RESPONSE_PROCESSING = Keywords.PRE_RESPONSE_PROCESSING -PRE_TRANSITIONS_PROCESSING = Keywords.PRE_TRANSITIONS_PROCESSING diff --git a/chatsky/script/core/normalization.py b/chatsky/script/core/normalization.py deleted file mode 100644 index 39b7dde8c..000000000 --- a/chatsky/script/core/normalization.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Normalization -------------- -Normalization module is used to normalize all python objects and functions to a format -that is suitable for script and actor execution process. -This module contains a basic set of functions for normalizing data in a dialog script. -""" - -from __future__ import annotations -import logging -from typing import Union, Callable, Optional, TYPE_CHECKING - -from .keywords import Keywords -from .context import Context -from .types import ConstLabel, ConditionType, Label, LabelType -from .message import Message - -if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline - -logger = logging.getLogger(__name__) - - -def normalize_label(label: Label, default_flow_label: LabelType = "") -> Label: - """ - The function that is used for normalization of - :py:const:`label `. - - :param label: If label is Callable the function is wrapped into try/except - and normalization is used on the result of the function call with the name label. - :param default_flow_label: flow_label is used if label does not contain flow_label. - :return: Result of the label normalization - """ - if callable(label): - - def get_label_handler(ctx: Context, pipeline: Pipeline) -> Optional[ConstLabel]: - try: - new_label = label(ctx, pipeline) - if new_label is None: - return None - new_label = normalize_label(new_label, default_flow_label) - flow_label, node_label, _ = new_label - node = pipeline.script.get(flow_label, {}).get(node_label) - if not node: - raise Exception(f"Unknown transitions {new_label} for pipeline.script={pipeline.script}") - if node_label in [Keywords.LOCAL, Keywords.GLOBAL]: - raise Exception(f"Invalid transition: can't transition to {flow_label}:{node_label}") - except Exception as exc: - new_label = None - logger.error(f"Exception {exc} of function {label}", exc_info=exc) - return new_label - - return get_label_handler # create wrap to get uniq key for dictionary - elif isinstance(label, str) or isinstance(label, Keywords): - return (default_flow_label, label, float("-inf")) - elif isinstance(label, tuple) and len(label) == 2 and isinstance(label[-1], float): - return (default_flow_label, label[0], label[-1]) - elif isinstance(label, tuple) and len(label) == 2 and isinstance(label[-1], str): - flow_label = label[0] or default_flow_label - return (flow_label, label[-1], float("-inf")) - elif isinstance(label, tuple) and len(label) == 3: - flow_label = label[0] or default_flow_label - return (flow_label, label[1], label[2]) - else: - raise TypeError(f"Label '{label!r}' is of incorrect type. It has to follow the `Label`:\n" f"{Label!r}") - - -def normalize_condition(condition: ConditionType) -> Callable[[Context, Pipeline], bool]: - """ - The function that is used to normalize `condition` - - :param condition: Condition to normalize. - :return: The function condition wrapped into the try/except. - """ - if callable(condition): - - def callable_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - try: - return condition(ctx, pipeline) - except Exception as exc: - logger.error(f"Exception {exc} of function {condition}", exc_info=exc) - return False - - return callable_condition_handler - - -def normalize_response( - response: Optional[Union[Message, Callable[[Context, "Pipeline"], Message]]] -) -> Callable[[Context, "Pipeline"], Message]: - """ - This function is used to normalize response. If the response is a Callable, it is returned, otherwise - the response is wrapped in an asynchronous function and this function is returned. - - :param response: Response to normalize. - :return: Function that returns callable response. - """ - if callable(response): - return response - else: - if response is None: - result = Message() - elif isinstance(response, Message): - result = response - else: - raise TypeError(type(response)) - - async def response_handler(ctx: Context, pipeline: Pipeline): - return result - - return response_handler diff --git a/chatsky/script/core/script.py b/chatsky/script/core/script.py deleted file mode 100644 index 985d6d30f..000000000 --- a/chatsky/script/core/script.py +++ /dev/null @@ -1,267 +0,0 @@ -""" -Script ------- -The Script module provides a set of `pydantic` models for representing the dialog graph. -These models are used to define the conversation flow, and to determine the appropriate response based on -the user's input and the current state of the conversation. -""" - -# %% -from __future__ import annotations -from enum import Enum -import inspect -import logging -from typing import Callable, List, Optional, Any, Dict, Tuple, Union, TYPE_CHECKING - -from pydantic import BaseModel, field_validator, validate_call - -from .types import Label, LabelType, ConditionType, ConstLabel # noqa: F401 -from .message import Message -from .keywords import Keywords -from .normalization import normalize_condition, normalize_label - -if TYPE_CHECKING: - from chatsky.script.core.context import Context - from chatsky.pipeline.pipeline.pipeline import Pipeline - -logger = logging.getLogger(__name__) - - -class UserFunctionType(str, Enum): - LABEL = "label" - RESPONSE = "response" - CONDITION = "condition" - TRANSITION_PROCESSING = "pre_transitions_processing" - RESPONSE_PROCESSING = "pre_response_processing" - - -USER_FUNCTION_TYPES: Dict[UserFunctionType, Tuple[Tuple[str, ...], str]] = { - UserFunctionType.LABEL: (("Context", "Pipeline"), "ConstLabel"), - UserFunctionType.RESPONSE: (("Context", "Pipeline"), "Message"), - UserFunctionType.CONDITION: (("Context", "Pipeline"), "bool"), - UserFunctionType.RESPONSE_PROCESSING: (("Context", "Pipeline"), "None"), - UserFunctionType.TRANSITION_PROCESSING: (("Context", "Pipeline"), "None"), -} - - -def _types_equal(signature_type: Any, expected_type: str) -> bool: - """ - This function checks equality of signature type with expected type. - Three cases are handled. If no signature is present, it is presumed that types are equal. - If signature is a type, it is compared with expected type as is. - If signature is a string, it is compared with expected type name. - - :param signature_type: type received from function signature. - :param expected_type: expected type - a class. - :return: true if types are equal, false otherwise. - """ - signature_str = signature_type.__name__ if hasattr(signature_type, "__name__") else str(signature_type) - signature_empty = signature_type == inspect.Parameter.empty - expected_string = signature_str == expected_type - expected_global = str(signature_type) == str(globals().get(expected_type)) - return signature_empty or expected_string or expected_global - - -def _validate_callable(callable: Callable, func_type: UserFunctionType, flow_label: str, node_label: str) -> List: - """ - This function validates a function during :py:class:`~chatsky.script.Script` validation. - It checks parameter number (unconditionally), parameter types (if specified) and return type (if specified). - - :param callable: Function to validate. - :param func_type: Type of the function (label, condition, response, etc.). - :param flow_label: Flow label this function is related to (used for error localization only). - :param node_label: Node label this function is related to (used for error localization only). - :return: list of produced error messages. - """ - - error_msgs = list() - signature = inspect.signature(callable) - arguments_type, return_type = USER_FUNCTION_TYPES[func_type] - params = list(signature.parameters.values()) - if len(params) != len(arguments_type): - msg = ( - f"Incorrect parameter number for {callable.__name__!r}: " - f"should be {len(arguments_type)}, not {len(params)}. " - f"Error found at {(flow_label, node_label)!r}." - ) - error_msgs.append(msg) - for idx, param in enumerate(params): - if not _types_equal(param.annotation, arguments_type[idx]): - msg = ( - f"Incorrect parameter annotation for parameter #{idx + 1} " - f" of {callable.__name__!r}: " - f"should be {arguments_type[idx]}, not {param.annotation}. " - f"Error found at {(flow_label, node_label)!r}." - ) - error_msgs.append(msg) - if not _types_equal(signature.return_annotation, return_type): - msg = ( - f"Incorrect return type annotation of {callable.__name__!r}: " - f"should be {return_type!r}, not {signature.return_annotation}. " - f"Error found at {(flow_label, node_label)!r}." - ) - error_msgs.append(msg) - return error_msgs - - -class Node(BaseModel, extra="forbid", validate_assignment=True): - """ - The class for the `Node` object. - """ - - transitions: Dict[Label, ConditionType] = {} - response: Optional[Union[Message, Callable[[Context, Pipeline], Message]]] = None - pre_transitions_processing: Dict[Any, Callable] = {} - pre_response_processing: Dict[Any, Callable] = {} - misc: dict = {} - - @field_validator("transitions", mode="before") - @classmethod - @validate_call - def normalize_transitions(cls, transitions: Dict[Label, ConditionType]) -> Dict[Label, Callable]: - """ - The function which is used to normalize transitions and returns normalized dict. - - :param transitions: Transitions to normalize. - :return: Transitions with normalized label and condition. - """ - transitions = { - normalize_label(label): normalize_condition(condition) for label, condition in transitions.items() - } - return transitions - - -class Script(BaseModel, extra="forbid"): - """ - The class for the `Script` object. - """ - - script: Dict[LabelType, Dict[LabelType, Node]] - - @field_validator("script", mode="before") - @classmethod - @validate_call - def normalize_script(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: - """ - This function normalizes :py:class:`.Script`: it returns dict where the GLOBAL node is moved - into the flow with the GLOBAL name. The function returns the structure - - `{GLOBAL: {...NODE...}, ...}` -> `{GLOBAL: {GLOBAL: {...NODE...}}, ...}`. - - :param script: :py:class:`.Script` that describes the dialog scenario. - :return: Normalized :py:class:`.Script`. - """ - if isinstance(script, dict): - if Keywords.GLOBAL in script and all( - [isinstance(item, Keywords) for item in script[Keywords.GLOBAL].keys()] - ): - script[Keywords.GLOBAL] = {Keywords.GLOBAL: script[Keywords.GLOBAL]} - return script - - @field_validator("script", mode="before") - @classmethod - @validate_call - def validate_script_before(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: - error_msgs = [] - for flow_name, flow in script.items(): - for node_name, node in flow.items(): - # validate labeling - transitions = node.get("transitions", dict()) - for label in transitions.keys(): - if callable(label): - error_msgs += _validate_callable(label, UserFunctionType.LABEL, flow_name, node_name) - - # validate responses - response = node.get("response", None) - if callable(response): - error_msgs += _validate_callable( - response, - UserFunctionType.RESPONSE, - flow_name, - node_name, - ) - - # validate conditions - for label, condition in transitions.items(): - if callable(condition): - error_msgs += _validate_callable( - condition, - UserFunctionType.CONDITION, - flow_name, - node_name, - ) - - # validate pre_transitions- and pre_response_processing - pre_transitions_processing = node.get("pre_transitions_processing", dict()) - pre_response_processing = node.get("pre_response_processing", dict()) - for place, functions in zip( - (UserFunctionType.TRANSITION_PROCESSING, UserFunctionType.RESPONSE_PROCESSING), - (pre_transitions_processing, pre_response_processing), - ): - for function in functions.values(): - if callable(function): - error_msgs += _validate_callable( - function, - place, - flow_name, - node_name, - ) - if error_msgs: - error_number_string = "1 error" if len(error_msgs) == 1 else f"{len(error_msgs)} errors" - raise ValueError( - f"Found {error_number_string}:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) - ) - else: - return script - - @field_validator("script", mode="after") - @classmethod - @validate_call - def validate_script_after(cls, script: Dict[LabelType, Any]) -> Dict[LabelType, Dict[LabelType, Dict[str, Any]]]: - error_msgs = [] - for flow_name, flow in script.items(): - for node_name, node in flow.items(): - # validate labeling - for label in node.transitions.keys(): - if not callable(label): - norm_flow_label, norm_node_label, _ = normalize_label(label, flow_name) - if norm_flow_label not in script.keys(): - msg = ( - f"Flow {norm_flow_label!r} cannot be found for label={label}. " - f"Error found at {(flow_name, node_name)!r}." - ) - elif norm_node_label not in script[norm_flow_label].keys(): - msg = ( - f"Node {norm_node_label!r} cannot be found for label={label}. " - f"Error found at {(flow_name, node_name)!r}." - ) - else: - msg = None - if msg is not None: - error_msgs.append(msg) - - if error_msgs: - error_number_string = "1 error" if len(error_msgs) == 1 else f"{len(error_msgs)} errors" - raise ValueError( - f"Found {error_number_string}:\n" + "\n".join([f"{i}) {er}" for i, er in enumerate(error_msgs, 1)]) - ) - else: - return script - - def __getitem__(self, key): - return self.script[key] - - def get(self, key, value=None): - return self.script.get(key, value) - - def keys(self): - return self.script.keys() - - def items(self): - return self.script.items() - - def values(self): - return self.script.values() - - def __iter__(self): - return self.script.__iter__() diff --git a/chatsky/script/core/types.py b/chatsky/script/core/types.py deleted file mode 100644 index 8655c96ad..000000000 --- a/chatsky/script/core/types.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Types ------ -The Types module contains a set of basic data types that -are used to define the expected input and output of various components of the framework. -The types defined in this module include basic data types such as strings -and lists, as well as more complex types that are specific to the framework. -""" - -from typing import Union, Callable, Tuple -from enum import Enum, auto -from typing_extensions import TypeAlias - -from .keywords import Keywords - -LabelType: TypeAlias = Union[str, Keywords] -"""Label can be a casual string or :py:class:`~chatsky.script.Keywords`.""" -# todo: rename these to identifiers - -NodeLabel1Type: TypeAlias = Tuple[str, float] -"""Label type for transitions can be `[node_name, transition_priority]`.""" - -NodeLabel2Type: TypeAlias = Tuple[str, str] -"""Label type for transitions can be `[flow_name, node_name]`.""" - -NodeLabel3Type: TypeAlias = Tuple[str, str, float] -"""Label type for transitions can be `[flow_name, node_name, transition_priority]`.""" - -NodeLabelTupledType: TypeAlias = Union[NodeLabel1Type, NodeLabel2Type, NodeLabel3Type] -"""Label type for transitions can be one of three different types.""" -# todo: group all these types into a class - -ConstLabel: TypeAlias = Union[NodeLabelTupledType, str] -"""Label functions should be annotated with this type only.""" - -Label: TypeAlias = Union[ConstLabel, Callable] -"""Label type for transitions should be of this type only.""" - -ConditionType: TypeAlias = Callable -"""Condition type can be only `Callable`.""" - - -class ActorStage(Enum): - """ - The class which holds keys for the handlers. These keys are used - for the actions of :py:class:`.Actor`. Each stage represents - a specific step in the conversation flow. Here is a brief description - of each stage. - """ - - CONTEXT_INIT = auto() - """ - This stage is used for the context initialization. - It involves setting up the conversation context. - """ - - GET_PREVIOUS_NODE = auto() - """ - This stage is used to retrieve the previous node. - """ - - REWRITE_PREVIOUS_NODE = auto() - """ - This stage is used to rewrite the previous node. - It involves updating the previous node in the conversation history - to reflect any changes made during the current conversation turn. - """ - - RUN_PRE_TRANSITIONS_PROCESSING = auto() - """ - This stage is used for running pre-transitions processing. - It involves performing any necessary pre-processing tasks. - """ - - GET_TRUE_LABELS = auto() - """ - This stage is used to retrieve the true labels. - It involves determining the correct label to take based - on the current conversation context. - """ - - GET_NEXT_NODE = auto() - """ - This stage is used to retrieve the next node in the conversation flow. - """ - - REWRITE_NEXT_NODE = auto() - """ - This stage is used to rewrite the next node. - It involves updating the next node in the conversation flow - to reflect any changes made during the current conversation turn. - """ - - RUN_PRE_RESPONSE_PROCESSING = auto() - """ - This stage is used for running pre-response processing. - It involves performing any necessary pre-processing tasks - before generating the response to the user. - """ - - CREATE_RESPONSE = auto() - """ - This stage is used for response creation. - It involves generating a response to the user based on the - current conversation context and any pre-processing performed. - """ - - FINISH_TURN = auto() - """ - This stage is used for finishing the current conversation turn. - It involves wrapping up any loose ends, such as saving context, - before waiting for the user's next input. - """ diff --git a/chatsky/script/extras/__init__.py b/chatsky/script/extras/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/script/extras/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/chatsky/script/extras/conditions/__init__.py b/chatsky/script/extras/conditions/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/script/extras/conditions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/chatsky/script/extras/slots/__init__.py b/chatsky/script/extras/slots/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/script/extras/slots/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/chatsky/script/labels/__init__.py b/chatsky/script/labels/__init__.py deleted file mode 100644 index a99fb0803..000000000 --- a/chatsky/script/labels/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from .std_labels import repeat, previous, to_start, to_fallback, forward, backward diff --git a/chatsky/script/labels/std_labels.py b/chatsky/script/labels/std_labels.py deleted file mode 100644 index a52aa37fc..000000000 --- a/chatsky/script/labels/std_labels.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Labels ------- -:py:const:`Labels ` are one of the important components of the dialog graph, -which determine the targeted node name of the transition. -They are used to identify the next step in the conversation. -Labels can also be used in combination with other conditions, -such as the current context or user data, to create more complex and dynamic conversations. - -This module contains a standard set of scripting :py:const:`labels ` that -can be used by developers to define the conversation flow. -""" - -from __future__ import annotations -from typing import Optional, Callable, TYPE_CHECKING -from chatsky.script import Context, ConstLabel - -if TYPE_CHECKING: - from chatsky.pipeline.pipeline.pipeline import Pipeline - - -def repeat(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: - """ - Returns transition handler that takes :py:class:`.Context`, - :py:class:`~chatsky.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` - to the last node with a given :py:const:`priority `. - If the priority is not given, `Pipeline.actor.label_priority` is used as default. - - :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. - """ - - def repeat_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: - current_priority = pipeline.actor.label_priority if priority is None else priority - if len(ctx.labels) >= 1: - flow_label, label = list(ctx.labels.values())[-1] - else: - flow_label, label = pipeline.actor.start_label[:2] - return (flow_label, label, current_priority) - - return repeat_transition_handler - - -def previous(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: - """ - Returns transition handler that takes :py:class:`~chatsky.script.Context`, - :py:class:`~chatsky.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` - to the previous node with a given :py:const:`priority `. - If the priority is not given, `Pipeline.actor.label_priority` is used as default. - If the current node is the start node, fallback is returned. - - :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. - """ - - def previous_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: - current_priority = pipeline.actor.label_priority if priority is None else priority - if len(ctx.labels) >= 2: - flow_label, label = list(ctx.labels.values())[-2] - elif len(ctx.labels) == 1: - flow_label, label = pipeline.actor.start_label[:2] - else: - flow_label, label = pipeline.actor.fallback_label[:2] - return (flow_label, label, current_priority) - - return previous_transition_handler - - -def to_start(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: - """ - Returns transition handler that takes :py:class:`~chatsky.script.Context`, - :py:class:`~chatsky.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` - to the start node with a given :py:const:`priority `. - If the priority is not given, `Pipeline.actor.label_priority` is used as default. - - :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. - """ - - def to_start_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: - current_priority = pipeline.actor.label_priority if priority is None else priority - return (*pipeline.actor.start_label[:2], current_priority) - - return to_start_transition_handler - - -def to_fallback(priority: Optional[float] = None) -> Callable[[Context, Pipeline], ConstLabel]: - """ - Returns transition handler that takes :py:class:`~chatsky.script.Context`, - :py:class:`~chatsky.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` - to the fallback node with a given :py:const:`priority `. - If the priority is not given, `Pipeline.actor.label_priority` is used as default. - - :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. - """ - - def to_fallback_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: - current_priority = pipeline.actor.label_priority if priority is None else priority - return (*pipeline.actor.fallback_label[:2], current_priority) - - return to_fallback_transition_handler - - -def _get_label_by_index_shifting( - ctx: Context, - pipeline: Pipeline, - priority: Optional[float] = None, - increment_flag: bool = True, - cyclicality_flag: bool = True, -) -> ConstLabel: - """ - Function that returns node label from the context and pipeline after shifting the index. - - :param ctx: Dialog context. - :param pipeline: Dialog pipeline. - :param priority: Priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. - :param increment_flag: If it is `True`, label index is incremented by `1`, - otherwise it is decreased by `1`. Defaults to `True`. - :param cyclicality_flag: If it is `True` the iteration over the label list is going cyclically - (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. - :return: The tuple that consists of `(flow_label, label, priority)`. - If fallback is executed `(flow_fallback_label, fallback_label, priority)` are returned. - """ - flow_label, node_label, current_priority = repeat(priority)(ctx, pipeline) - labels = list(pipeline.script.get(flow_label, {})) - - if node_label not in labels: - return (*pipeline.actor.fallback_label[:2], current_priority) - - label_index = labels.index(node_label) - label_index = label_index + 1 if increment_flag else label_index - 1 - if not (cyclicality_flag or (0 <= label_index < len(labels))): - return (*pipeline.actor.fallback_label[:2], current_priority) - label_index %= len(labels) - - return (flow_label, labels[label_index], current_priority) - - -def forward( - priority: Optional[float] = None, cyclicality_flag: bool = True -) -> Callable[[Context, Pipeline], ConstLabel]: - """ - Returns transition handler that takes :py:class:`~chatsky.script.Context`, - :py:class:`~chatsky.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` - to the forward node with a given :py:const:`priority ` and :py:const:`cyclicality_flag `. - If the priority is not given, `Pipeline.actor.label_priority` is used as default. - - :param priority: Float priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. - :param cyclicality_flag: If it is `True`, the iteration over the label list is going cyclically - (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. - """ - - def forward_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: - return _get_label_by_index_shifting( - ctx, pipeline, priority, increment_flag=True, cyclicality_flag=cyclicality_flag - ) - - return forward_transition_handler - - -def backward( - priority: Optional[float] = None, cyclicality_flag: bool = True -) -> Callable[[Context, Pipeline], ConstLabel]: - """ - Returns transition handler that takes :py:class:`~chatsky.script.Context`, - :py:class:`~chatsky.pipeline.Pipeline` and :py:const:`priority `. - This handler returns a :py:const:`label ` - to the backward node with a given :py:const:`priority ` and :py:const:`cyclicality_flag `. - If the priority is not given, `Pipeline.actor.label_priority` is used as default. - - :param priority: Float priority of transition. Uses `Pipeline.actor.label_priority` if priority not defined. - :param cyclicality_flag: If it is `True`, the iteration over the label list is going cyclically - (e.g the element with `index = len(labels)` has `index = 0`). Defaults to `True`. - """ - - def back_transition_handler(ctx: Context, pipeline: Pipeline) -> ConstLabel: - return _get_label_by_index_shifting( - ctx, pipeline, priority, increment_flag=False, cyclicality_flag=cyclicality_flag - ) - - return back_transition_handler diff --git a/chatsky/script/responses/__init__.py b/chatsky/script/responses/__init__.py deleted file mode 100644 index fe2f294ea..000000000 --- a/chatsky/script/responses/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from .std_responses import choice diff --git a/chatsky/script/responses/std_responses.py b/chatsky/script/responses/std_responses.py deleted file mode 100644 index 060b6e264..000000000 --- a/chatsky/script/responses/std_responses.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Responses ---------- -Responses determine the response that will be sent to the user for each node of the dialog graph. -Responses are used to provide the user with information, ask questions, -or guide the conversation in a particular direction. - -This module provides only one predefined response function that can be used to quickly -respond to the user and keep the conversation flowing. -""" - -import random -from typing import List - -from chatsky.pipeline import Pipeline -from chatsky.script import Context, Message - - -def choice(responses: List[Message]): - """ - Function wrapper that takes the list of responses as an input - and returns handler which outputs a response randomly chosen from that list. - - :param responses: A list of responses for random sampling. - """ - - def choice_response_handler(ctx: Context, pipeline: Pipeline): - return random.choice(responses) - - return choice_response_handler diff --git a/chatsky/slots/__init__.py b/chatsky/slots/__init__.py index c0a22623c..6c929b9af 100644 --- a/chatsky/slots/__init__.py +++ b/chatsky/slots/__init__.py @@ -1,7 +1 @@ -# -*- coding: utf-8 -*- -# flake8: noqa: F401 - from chatsky.slots.slots import GroupSlot, ValueSlot, RegexpSlot, FunctionSlot -from chatsky.slots.conditions import slots_extracted -from chatsky.slots.processing import extract, extract_all, unset, unset_all, fill_template -from chatsky.slots.response import filled_template diff --git a/chatsky/slots/conditions.py b/chatsky/slots/conditions.py deleted file mode 100644 index d2e3f9d33..000000000 --- a/chatsky/slots/conditions.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Conditions ---------------------------- -Provides slot-related conditions. -""" - -from __future__ import annotations -from typing import TYPE_CHECKING, Literal - -if TYPE_CHECKING: - from chatsky.script import Context - from chatsky.slots.slots import SlotName - from chatsky.pipeline import Pipeline - - -def slots_extracted(*slots: SlotName, mode: Literal["any", "all"] = "all"): - """ - Conditions that checks if slots are extracted. - - :param slots: Names for slots that need to be checked. - :param mode: Whether to check if all slots are extracted or any slot is extracted. - """ - - def check_slot_state(ctx: Context, pipeline: Pipeline) -> bool: - manager = ctx.framework_data.slot_manager - if mode == "all": - return all(manager.is_slot_extracted(slot) for slot in slots) - elif mode == "any": - return any(manager.is_slot_extracted(slot) for slot in slots) - raise ValueError(f"{mode!r} not in ['any', 'all'].") - - return check_slot_state diff --git a/chatsky/slots/processing.py b/chatsky/slots/processing.py deleted file mode 100644 index df3df43f9..000000000 --- a/chatsky/slots/processing.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Processing ---------------------------- -This module provides wrappers for :py:class:`~chatsky.slots.slots.SlotManager`'s API. -""" - -from __future__ import annotations - -import logging -from typing import Awaitable, Callable, TYPE_CHECKING - -if TYPE_CHECKING: - from chatsky.slots.slots import SlotName - from chatsky.script import Context - from chatsky.pipeline import Pipeline - -logger = logging.getLogger(__name__) - - -def extract(*slots: SlotName) -> Callable[[Context, Pipeline], Awaitable[None]]: - """ - Extract slots listed slots. - This will override all slots even if they are already extracted. - - :param slots: List of slot names to extract. - """ - - async def inner(ctx: Context, pipeline: Pipeline) -> None: - manager = ctx.framework_data.slot_manager - for slot in slots: # todo: maybe gather - await manager.extract_slot(slot, ctx, pipeline) - - return inner - - -def extract_all(): - """ - Extract all slots defined in the pipeline. - """ - - async def inner(ctx: Context, pipeline: Pipeline): - manager = ctx.framework_data.slot_manager - await manager.extract_all(ctx, pipeline) - - return inner - - -def unset(*slots: SlotName) -> Callable[[Context, Pipeline], None]: - """ - Mark specified slots as not extracted and clear extracted values. - - :param slots: List of slot names to extract. - """ - - def unset_inner(ctx: Context, pipeline: Pipeline) -> None: - manager = ctx.framework_data.slot_manager - for slot in slots: - manager.unset_slot(slot) - - return unset_inner - - -def unset_all(): - """ - Mark all slots as not extracted and clear all extracted values. - """ - - def inner(ctx: Context, pipeline: Pipeline): - manager = ctx.framework_data.slot_manager - manager.unset_all_slots() - - return inner - - -def fill_template() -> Callable[[Context, Pipeline], None]: - """ - Fill the response template in the current node. - - Response message of the current node should be a format-string: e.g. "Your username is {profile.username}". - """ - - def inner(ctx: Context, pipeline: Pipeline) -> None: - manager = ctx.framework_data.slot_manager - # get current node response - response = ctx.current_node.response - - if response is None: - return - - if callable(response): - response = response(ctx, pipeline) - - new_text = manager.fill_template(response.text) - - response.text = new_text - ctx.current_node.response = response - - return inner diff --git a/chatsky/slots/response.py b/chatsky/slots/response.py deleted file mode 100644 index 473960704..000000000 --- a/chatsky/slots/response.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Response ---------------------------- -Slot-related Chatsky responses. -""" - -from __future__ import annotations -from typing import Callable, TYPE_CHECKING - -if TYPE_CHECKING: - from chatsky.script import Context, Message - from chatsky.pipeline import Pipeline - - -def filled_template(template: Message) -> Callable[[Context, Pipeline], Message]: - """ - Fill template with slot values. - The `text` attribute of the template message should be a format-string: - e.g. "Your username is {profile.username}". - - For the example above, if ``profile.username`` slot has value "admin", - it would return a copy of the message with the following text: - "Your username is admin". - - :param template: Template message with a format-string text. - """ - - def fill_inner(ctx: Context, pipeline: Pipeline) -> Message: - message = template.model_copy() - new_text = ctx.framework_data.slot_manager.fill_template(template.text) - message.text = new_text - return message - - return fill_inner diff --git a/chatsky/slots/slots.py b/chatsky/slots/slots.py index 29dc44b9a..a6981fabc 100644 --- a/chatsky/slots/slots.py +++ b/chatsky/slots/slots.py @@ -9,19 +9,19 @@ import asyncio import re from abc import ABC, abstractmethod -from typing import Callable, Any, Awaitable, TYPE_CHECKING, Union -from typing_extensions import TypeAlias +from typing import Callable, Any, Awaitable, TYPE_CHECKING, Union, Optional, Dict +from typing_extensions import TypeAlias, Annotated import logging from functools import reduce +from string import Formatter -from pydantic import BaseModel, model_validator, Field +from pydantic import BaseModel, model_validator, Field, field_serializer, field_validator from chatsky.utils.devel.async_helpers import wrap_sync_function_in_async -from chatsky.utils.devel.json_serialization import PickleEncodedValue +from chatsky.utils.devel.json_serialization import pickle_serializer, pickle_validator if TYPE_CHECKING: - from chatsky.script import Context, Message - from chatsky.pipeline.pipeline.pipeline import Pipeline + from chatsky.core import Context, Message logger = logging.getLogger(__name__) @@ -88,7 +88,7 @@ class ExtractedSlot(BaseModel, ABC): Represents value of an extracted slot. Instances of this class are managed by framework and - are stored in :py:attr:`~chatsky.script.core.context.FrameworkData.slot_manager`. + are stored in :py:attr:`~chatsky.core.context.FrameworkData.slot_manager`. They can be accessed via the ``ctx.framework_data.slot_manager.get_extracted_slot`` method. """ @@ -112,8 +112,29 @@ class ExtractedValueSlot(ExtractedSlot): """Value extracted from :py:class:`~.ValueSlot`.""" is_slot_extracted: bool - extracted_value: PickleEncodedValue - default_value: PickleEncodedValue = None + extracted_value: Any + default_value: Any = None + + @field_serializer("extracted_value", "default_value", when_used="json") + def pickle_serialize_values(self, value): + """ + Cast values to string via pickle. + Allows storing arbitrary data in these fields when using context storages. + """ + if value is not None: + return pickle_serializer(value) + return value + + @field_validator("extracted_value", "default_value", mode="before") + @classmethod + def pickle_validate_values(cls, value): + """ + Restore values after being processed with + :py:meth:`pickle_serialize_values`. + """ + if value is not None: + return pickle_validator(value) + return value @property def __slot_extracted__(self) -> bool: @@ -133,7 +154,9 @@ def __str__(self): class ExtractedGroupSlot(ExtractedSlot, extra="allow"): - __pydantic_extra__: dict[str, Union["ExtractedValueSlot", "ExtractedGroupSlot"]] + __pydantic_extra__: Dict[ + str, Annotated[Union["ExtractedGroupSlot", "ExtractedValueSlot"], Field(union_mode="left_to_right")] + ] @property def __slot_extracted__(self) -> bool: @@ -171,7 +194,7 @@ class BaseSlot(BaseModel, frozen=True): """ @abstractmethod - async def get_value(self, ctx: Context, pipeline: Pipeline) -> ExtractedSlot: + async def get_value(self, ctx: Context) -> ExtractedSlot: """ Extract slot value from :py:class:`~.Context` and return an instance of :py:class:`~.ExtractedSlot`. """ @@ -194,7 +217,7 @@ class ValueSlot(BaseSlot, frozen=True): default_value: Any = None @abstractmethod - async def extract_value(self, ctx: Context, pipeline: Pipeline) -> Union[Any, SlotNotExtracted]: + async def extract_value(self, ctx: Context) -> Union[Any, SlotNotExtracted]: """ Return value extracted from context. @@ -204,13 +227,13 @@ async def extract_value(self, ctx: Context, pipeline: Pipeline) -> Union[Any, Sl """ raise NotImplementedError - async def get_value(self, ctx: Context, pipeline: Pipeline) -> ExtractedValueSlot: + async def get_value(self, ctx: Context) -> ExtractedValueSlot: """Wrapper for :py:meth:`~.ValueSlot.extract_value` to handle exceptions.""" extracted_value = SlotNotExtracted("Caught an exit exception.") is_slot_extracted = False try: - extracted_value = await self.extract_value(ctx, pipeline) + extracted_value = await wrap_sync_function_in_async(self.extract_value, ctx) is_slot_extracted = not isinstance(extracted_value, SlotNotExtracted) except Exception as error: logger.exception(f"Exception occurred during {self.__class__.__name__!r} extraction.", exc_info=error) @@ -235,7 +258,7 @@ class GroupSlot(BaseSlot, extra="allow", frozen=True): Base class for :py:class:`~.RootSlot` and :py:class:`~.GroupSlot`. """ - __pydantic_extra__: dict[str, Union["ValueSlot", "GroupSlot"]] + __pydantic_extra__: Dict[str, Annotated[Union["GroupSlot", "ValueSlot"], Field(union_mode="left_to_right")]] def __init__(self, **kwargs): # supress unexpected argument warnings super().__init__(**kwargs) @@ -252,10 +275,8 @@ def __check_extra_field_names__(self): raise ValueError(f"Extra field names cannot be dunder: {field!r}") return self - async def get_value(self, ctx: Context, pipeline: Pipeline) -> ExtractedGroupSlot: - child_values = await asyncio.gather( - *(child.get_value(ctx, pipeline) for child in self.__pydantic_extra__.values()) - ) + async def get_value(self, ctx: Context) -> ExtractedGroupSlot: + child_values = await asyncio.gather(*(child.get_value(ctx) for child in self.__pydantic_extra__.values())) return ExtractedGroupSlot( **{child_name: child_value for child_value, child_name in zip(child_values, self.__pydantic_extra__.keys())} ) @@ -278,7 +299,7 @@ class RegexpSlot(ValueSlot, frozen=True): match_group_idx: int = 0 "Index of the group to match." - async def extract_value(self, ctx: Context, _: Pipeline) -> Union[str, SlotNotExtracted]: + async def extract_value(self, ctx: Context) -> Union[str, SlotNotExtracted]: request_text = ctx.last_request.text search = re.search(self.regexp, request_text) return ( @@ -297,7 +318,7 @@ class FunctionSlot(ValueSlot, frozen=True): func: Callable[[Message], Union[Awaitable[Union[Any, SlotNotExtracted]], Any, SlotNotExtracted]] - async def extract_value(self, ctx: Context, _: Pipeline) -> Union[Any, SlotNotExtracted]: + async def extract_value(self, ctx: Context) -> Union[Any, SlotNotExtracted]: return await wrap_sync_function_in_async(self.func, ctx.last_request) @@ -336,43 +357,37 @@ def get_slot(self, slot_name: SlotName) -> BaseSlot: :raises KeyError: If the slot with the specified name does not exist. """ - try: - slot = recursive_getattr(self.root_slot, slot_name) - if isinstance(slot, BaseSlot): - return slot - except (AttributeError, KeyError): - pass + slot = recursive_getattr(self.root_slot, slot_name) + if isinstance(slot, BaseSlot): + return slot raise KeyError(f"Could not find slot {slot_name!r}.") - async def extract_slot(self, slot_name: SlotName, ctx: Context, pipeline: Pipeline) -> None: + async def extract_slot(self, slot_name: SlotName, ctx: Context) -> None: """ Extract slot `slot_name` and store extracted value in `slot_storage`. :raises KeyError: If the slot with the specified name does not exist. """ slot = self.get_slot(slot_name) - value = await slot.get_value(ctx, pipeline) + value = await slot.get_value(ctx) recursive_setattr(self.slot_storage, slot_name, value) - async def extract_all(self, ctx: Context, pipeline: Pipeline): + async def extract_all(self, ctx: Context): """ Extract all slots from slot configuration `root_slot` and set `slot_storage` to the extracted value. """ - self.slot_storage = await self.root_slot.get_value(ctx, pipeline) + self.slot_storage = await self.root_slot.get_value(ctx) - def get_extracted_slot(self, slot_name: SlotName) -> ExtractedSlot: + def get_extracted_slot(self, slot_name: SlotName) -> Union[ExtractedValueSlot, ExtractedGroupSlot]: """ Retrieve extracted value from `slot_storage`. :raises KeyError: If the slot with the specified name does not exist. """ - try: - slot = recursive_getattr(self.slot_storage, slot_name) - if isinstance(slot, ExtractedSlot): - return slot - except (AttributeError, KeyError): - pass + slot = recursive_getattr(self.slot_storage, slot_name) + if isinstance(slot, ExtractedSlot): + return slot raise KeyError(f"Could not find slot {slot_name!r}.") def is_slot_extracted(self, slot_name: str) -> bool: @@ -403,9 +418,14 @@ def unset_all_slots(self) -> None: """ self.slot_storage.__unset__() - def fill_template(self, template: str) -> str: + class KwargOnlyFormatter(Formatter): + def get_value(self, key, args, kwargs): + return super().get_value(str(key), args, kwargs) + + def fill_template(self, template: str) -> Optional[str]: """ - Fill `template` string with extracted slot values and return a formatted string. + Fill `template` string with extracted slot values and return a formatted string + or None if an exception has occurred while trying to fill template. `template` should be a format-string: @@ -415,4 +435,8 @@ def fill_template(self, template: str) -> str: it would return the following text: "Your username is admin". """ - return template.format(**dict(self.slot_storage.__pydantic_extra__.items())) + try: + return self.KwargOnlyFormatter().format(template, **dict(self.slot_storage.__pydantic_extra__.items())) + except Exception as exc: + logger.exception("An exception occurred during template filling.", exc_info=exc) + return None diff --git a/chatsky/stats/default_extractors.py b/chatsky/stats/default_extractors.py index e390148f5..0819dac4f 100644 --- a/chatsky/stats/default_extractors.py +++ b/chatsky/stats/default_extractors.py @@ -13,8 +13,8 @@ from datetime import datetime -from chatsky.script import Context -from chatsky.pipeline import ExtraHandlerRuntimeInfo, Pipeline +from chatsky.core import Context, Pipeline +from chatsky.core.service.extra import ExtraHandlerRuntimeInfo from .utils import get_extra_handler_name @@ -29,9 +29,11 @@ async def get_current_label(ctx: Context, pipeline: Pipeline, info: ExtraHandler """ last_label = ctx.last_label - if last_label is None: - last_label = pipeline.actor.start_label[:2] - return {"flow": last_label[0], "node": last_label[1], "label": ": ".join(last_label)} + return { + "flow": last_label.flow_name, + "node": last_label.node_name, + "label": f"{last_label.flow_name}: {last_label.node_name}", + } async def get_timing_before(ctx: Context, _, info: ExtraHandlerRuntimeInfo): @@ -59,7 +61,7 @@ async def get_timing_after(ctx: Context, _, info: ExtraHandlerRuntimeInfo): # n async def get_last_response(ctx: Context, _, info: ExtraHandlerRuntimeInfo): """ Extract the text of the last response in the current context. - This handler is best used together with the `ACTOR` component. + This handler is best used together with the `Actor` component. This function is required to enable charts that aggregate requests and responses. """ @@ -70,7 +72,7 @@ async def get_last_response(ctx: Context, _, info: ExtraHandlerRuntimeInfo): async def get_last_request(ctx: Context, _, info: ExtraHandlerRuntimeInfo): """ Extract the text of the last request in the current context. - This handler is best used together with the `ACTOR` component. + This handler is best used together with the `Actor` component. This function is required to enable charts that aggregate requests and responses. """ diff --git a/chatsky/stats/instrumentor.py b/chatsky/stats/instrumentor.py index a395c7b4c..b9b68dee1 100644 --- a/chatsky/stats/instrumentor.py +++ b/chatsky/stats/instrumentor.py @@ -26,7 +26,7 @@ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter -from chatsky.script.core.context import get_last_index +from chatsky.core.context import get_last_index from chatsky.stats.utils import ( resource, get_extra_handler_name, @@ -161,7 +161,7 @@ async def __call__(self, wrapped, _, args, kwargs): pipeline_component = get_extra_handler_name(info) attributes = { "context_id": str(ctx.id), - "request_id": get_last_index(ctx.requests), + "request_id": get_last_index(ctx.labels), "pipeline_component": pipeline_component, } diff --git a/chatsky/stats/utils.py b/chatsky/stats/utils.py index 51ac9ad4d..8147f7276 100644 --- a/chatsky/stats/utils.py +++ b/chatsky/stats/utils.py @@ -33,7 +33,7 @@ from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter, LogExporter from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter, MetricExporter -from chatsky.pipeline import ExtraHandlerRuntimeInfo +from chatsky.core.service.extra import ExtraHandlerRuntimeInfo SERVICE_NAME = "chatsky" diff --git a/chatsky/utils/db_benchmark/basic_config.py b/chatsky/utils/db_benchmark/basic_config.py index 11e744dd0..68d9c1006 100644 --- a/chatsky/utils/db_benchmark/basic_config.py +++ b/chatsky/utils/db_benchmark/basic_config.py @@ -15,7 +15,7 @@ from humanize import naturalsize from pympler import asizeof -from chatsky.script import Message, Context +from chatsky.core import Message, Context from chatsky.utils.db_benchmark.benchmark import BenchmarkConfig diff --git a/chatsky/utils/db_benchmark/benchmark.py b/chatsky/utils/db_benchmark/benchmark.py index f1132d283..fee678e66 100644 --- a/chatsky/utils/db_benchmark/benchmark.py +++ b/chatsky/utils/db_benchmark/benchmark.py @@ -33,7 +33,7 @@ from tqdm.auto import tqdm from chatsky.context_storages import DBContextStorage -from chatsky.script import Context +from chatsky.core import Context def time_context_read_write( diff --git a/chatsky/utils/devel/__init__.py b/chatsky/utils/devel/__init__.py index affbce004..e7227f8c4 100644 --- a/chatsky/utils/devel/__init__.py +++ b/chatsky/utils/devel/__init__.py @@ -6,8 +6,10 @@ """ from .json_serialization import ( - JSONSerializableDict, - PickleEncodedValue, + json_pickle_serializer, + json_pickle_validator, + pickle_serializer, + pickle_validator, JSONSerializableExtras, ) from .extra_field_helpers import grab_extra_fields diff --git a/chatsky/utils/devel/json_serialization.py b/chatsky/utils/devel/json_serialization.py index f198dc47c..132e79f65 100644 --- a/chatsky/utils/devel/json_serialization.py +++ b/chatsky/utils/devel/json_serialization.py @@ -17,11 +17,9 @@ from copy import deepcopy from pickle import dumps, loads from typing import Any, Dict, List, Union -from typing_extensions import Annotated, TypeAlias +from typing_extensions import TypeAlias from pydantic import ( JsonValue, - PlainSerializer, - PlainValidator, RootModel, BaseModel, model_validator, @@ -121,43 +119,6 @@ def json_pickle_validator(model: Serializable) -> Serializable: return model_copy -PickleSerializer = PlainSerializer(pickle_serializer, when_used="json") -"""Pydantic wrapper of :py:func:`~.pickle_serializer`.""" -PickleValidator = PlainValidator(pickle_validator) -"""Pydantic wrapper of :py:func:`~.pickle_validator`.""" -PickleEncodedValue = Annotated[Any, PickleSerializer, PickleValidator] -""" -Annotation for field that makes it JSON serializable via pickle: - -This field is always a normal object when inside its class but is a string encoding of the object -outside of the class -- either after serialization or before initialization. -As such this field cannot be used during initialization and the only way to use it is to bypass validation. - -.. code:: python - - class MyClass(BaseModel): - my_field: Optional[PickleEncodedValue] = None # the field must have a default value - - my_obj = MyClass() # the field cannot be set during init - my_obj.my_field = unserializable_object # can be set manually to avoid validation - -Alternatively, ``BaseModel.model_construct`` may be used to bypass validation, -though it would bypass validation of all fields. -""" - -JSONPickleSerializer = PlainSerializer(json_pickle_serializer, when_used="json") -"""Pydantic wrapper of :py:func:`~.json_pickle_serializer`.""" -JSONPickleValidator = PlainValidator(json_pickle_validator) -"""Pydantic wrapper of :py:func:`~.json_pickle_validator`.""" -JSONSerializableDict = Annotated[Serializable, JSONPickleSerializer, JSONPickleValidator] -""" -Annotation for dictionary or Pydantic model that makes all its fields JSON serializable. - -This uses a reserved dictionary key :py:data:`~._JSON_EXTRA_FIELDS_KEYS` to store -fields serialized that way. -""" - - class JSONSerializableExtras(BaseModel, extra="allow"): """ This model makes extra fields pickle-serializable. diff --git a/chatsky/utils/parser/__init__.py b/chatsky/utils/parser/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/utils/parser/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/chatsky/utils/testing/__init__.py b/chatsky/utils/testing/__init__.py index 2081e6dd4..bfbe04fef 100644 --- a/chatsky/utils/testing/__init__.py +++ b/chatsky/utils/testing/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from .common import is_interactive_mode, check_happy_path, run_interactive_mode +from .common import is_interactive_mode, check_happy_path from .toy_script import TOY_SCRIPT, TOY_SCRIPT_KWARGS, HAPPY_PATH -from .response_comparers import default_comparer try: import pytest diff --git a/chatsky/utils/testing/common.py b/chatsky/utils/testing/common.py index 6f8890ff8..c884a513f 100644 --- a/chatsky/utils/testing/common.py +++ b/chatsky/utils/testing/common.py @@ -5,12 +5,11 @@ """ from os import getenv -from typing import Callable, Tuple, Optional, Union +from typing import Tuple, Iterable from uuid import uuid4 -from chatsky.script import Context, Message -from chatsky.pipeline import Pipeline -from chatsky.utils.testing.response_comparers import default_comparer +from chatsky.core import Message, Pipeline +from chatsky.core.message import MessageInitTypes def is_interactive_mode() -> bool: # pragma: no cover @@ -32,67 +31,41 @@ def is_interactive_mode() -> bool: # pragma: no cover def check_happy_path( pipeline: Pipeline, - happy_path: Tuple[Tuple[Union[str, Message], Union[str, Message]], ...], - # This optional argument is used for additional processing of candidate responses and reference responses - response_comparer: Callable[[Message, Message, Context], Optional[str]] = default_comparer, - printout_enable: bool = True, + happy_path: Iterable[Tuple[MessageInitTypes, MessageInitTypes]], + *, + response_comparator=Message.__eq__, + printout: bool = False, ): """ Running tutorial with provided pipeline for provided requests, comparing responses with correct expected responses. - In cases when additional processing of responses is needed (e.g. in case of response being an HTML string), - a special function (response comparer) is used. :param pipeline: The Pipeline instance, that will be used for checking. :param happy_path: A tuple of (request, response) tuples, so-called happy path, its requests are passed to pipeline and the pipeline responses are compared to its responses. - :param response_comparer: A special comparer function that accepts received response, true response and context; - it returns `None` is two responses are equal and transformed received response if they are different. - :param printout_enable: A flag that enables requests and responses fancy printing (to STDOUT). + :param response_comparator: + Function that checks reference response (first argument) with the actual response (second argument). + Defaults to ``Message.__eq__``. + :param printout: Whether to print the requests/responses during iteration. """ - ctx_id = uuid4() # get random ID for current context for step_id, (request_raw, reference_response_raw) in enumerate(happy_path): - request = Message(text=request_raw) if isinstance(request_raw, str) else request_raw - reference_response = ( - Message(text=reference_response_raw) if isinstance(reference_response_raw, str) else reference_response_raw - ) - ctx = pipeline(request, ctx_id) - candidate_response = ctx.last_response - if printout_enable: - print(f"(user) >>> {repr(request)}") - print(f" (bot) <<< {repr(candidate_response)}") - if candidate_response is None: - raise Exception( - f"\n\npipeline = {pipeline.info_dict}\n\n" - f"ctx = {ctx}\n\n" - f"step_id = {step_id}\n" - f"request = {repr(request)}\n" - "Candidate response is None." - ) - parsed_response_with_deviation = response_comparer(candidate_response, reference_response, ctx) - if parsed_response_with_deviation is not None: - raise Exception( - f"\n\npipeline = {pipeline.info_dict}\n\n" - f"ctx = {ctx}\n\n" - f"step_id = {step_id}\n" - f"request = {repr(request)}\n" - f"candidate_response = {repr(parsed_response_with_deviation)}\n" - f"reference_response = {repr(reference_response)}\n" - "candidate_response != reference_response" - ) + request = Message.model_validate(request_raw) + reference_response = Message.model_validate(reference_response_raw) + if printout: + print(f"USER: {request}") -def run_interactive_mode(pipeline: Pipeline): # pragma: no cover - """ - Running tutorial with provided pipeline in interactive mode, just like with CLI messenger interface. - The dialog won't be stored anywhere, it will only be outputted to STDOUT. + ctx = pipeline(request, ctx_id) - :param pipeline: The Pipeline instance, that will be used for running. - """ + actual_response = ctx.last_response + if printout: + print(f"BOT : {actual_response}") - ctx_id = uuid4() # Random UID - print("Start a dialogue with the bot") - while True: - request = input(">>> ") - ctx = pipeline(request=Message(request), ctx_id=ctx_id) - print(f"<<< {repr(ctx.last_response)}") + if not response_comparator(reference_response, actual_response): + raise AssertionError( + f"""check_happy_path failed +step id: {step_id} +reference response: {reference_response} +actual response: {actual_response} +""" + ) diff --git a/chatsky/utils/testing/response_comparers.py b/chatsky/utils/testing/response_comparers.py deleted file mode 100644 index dd6c9189a..000000000 --- a/chatsky/utils/testing/response_comparers.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Response comparer ------------------ -This module defines function used to compare two response objects. -""" - -from typing import Any, Optional - -from chatsky.script import Context, Message - - -def default_comparer(candidate: Message, reference: Message, _: Context) -> Optional[Any]: - """ - The default response comparer. Literally compares two response objects. - - :param candidate: The received (candidate) response. - :param reference: The true (reference) response. - :param _: Current Context (unused). - :return: `None` if two responses are equal or candidate response otherwise. - """ - return None if candidate == reference else candidate diff --git a/chatsky/utils/testing/toy_script.py b/chatsky/utils/testing/toy_script.py index f4327e180..fdeae8117 100644 --- a/chatsky/utils/testing/toy_script.py +++ b/chatsky/utils/testing/toy_script.py @@ -5,31 +5,30 @@ in tutorials. """ -from chatsky.script.conditions import exact_match -from chatsky.script import TRANSITIONS, RESPONSE, Message +from chatsky.conditions import ExactMatch +from chatsky.core import TRANSITIONS, RESPONSE, Transition as Tr TOY_SCRIPT = { "greeting_flow": { "start_node": { - RESPONSE: Message(), - TRANSITIONS: {"node1": exact_match("Hi")}, + TRANSITIONS: [Tr(dst="node1", cnd=ExactMatch("Hi"))], }, "node1": { - RESPONSE: Message("Hi, how are you?"), - TRANSITIONS: {"node2": exact_match("i'm fine, how are you?")}, + RESPONSE: "Hi, how are you?", + TRANSITIONS: [Tr(dst="node2", cnd=ExactMatch("i'm fine, how are you?"))], }, "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: {"node3": exact_match("Let's talk about music.")}, + RESPONSE: "Good. What do you want to talk about?", + TRANSITIONS: [Tr(dst="node3", cnd=ExactMatch("Let's talk about music."))], }, "node3": { - RESPONSE: Message("Sorry, I can not talk about music now."), - TRANSITIONS: {"node4": exact_match("Ok, goodbye.")}, + RESPONSE: "Sorry, I can not talk about music now.", + TRANSITIONS: [Tr(dst="node4", cnd=ExactMatch("Ok, goodbye."))], }, - "node4": {RESPONSE: Message("bye"), TRANSITIONS: {"node1": exact_match("Hi")}}, + "node4": {RESPONSE: "bye", TRANSITIONS: [Tr(dst="node1", cnd=ExactMatch("Hi"))]}, "fallback_node": { - RESPONSE: Message("Ooops"), - TRANSITIONS: {"node1": exact_match("Hi")}, + RESPONSE: "Ooops", + TRANSITIONS: [Tr(dst="node1", cnd=ExactMatch("Hi"))], }, } } @@ -46,7 +45,7 @@ } """ # There should be a better description of this -Keyword arguments to pass to :py:meth:`~chatsky.pipeline.pipeline.pipeline.Pipeline` in order to +Keyword arguments to pass to :py:meth:`~chatsky.core.pipeline.Pipeline` in order to use :py:data:`~.TOY_SCRIPT`: .. code-block:: @@ -72,98 +71,98 @@ MULTIFLOW_SCRIPT = { "root": { "start": { - RESPONSE: Message("Hi"), - TRANSITIONS: { - ("small_talk", "ask_some_questions"): exact_match("hi"), - ("animals", "have_pets"): exact_match("i like animals"), - ("animals", "like_animals"): exact_match("let's talk about animals"), - ("news", "what_news"): exact_match("let's talk about news"), - }, - }, - "fallback": {RESPONSE: Message("Oops")}, + RESPONSE: "Hi", + TRANSITIONS: [ + Tr(dst=("small_talk", "ask_some_questions"), cnd=ExactMatch("hi")), + Tr(dst=("animals", "have_pets"), cnd=ExactMatch("i like animals")), + Tr(dst=("animals", "like_animals"), cnd=ExactMatch("let's talk about animals")), + Tr(dst=("news", "what_news"), cnd=ExactMatch("let's talk about news")), + ], + }, + "fallback": {RESPONSE: "Oops", TRANSITIONS: [Tr(dst="start")]}, }, "animals": { "have_pets": { - RESPONSE: Message("do you have pets?"), - TRANSITIONS: {"what_animal": exact_match("yes")}, + RESPONSE: "do you have pets?", + TRANSITIONS: [Tr(dst="what_animal", cnd=ExactMatch("yes"))], }, "like_animals": { - RESPONSE: Message("do you like it?"), - TRANSITIONS: {"what_animal": exact_match("yes")}, + RESPONSE: "do you like it?", + TRANSITIONS: [Tr(dst="what_animal", cnd=ExactMatch("yes"))], }, "what_animal": { - RESPONSE: Message("what animals do you have?"), - TRANSITIONS: { - "ask_about_color": exact_match("bird"), - "ask_about_breed": exact_match("dog"), - }, + RESPONSE: "what animals do you have?", + TRANSITIONS: [ + Tr(dst="ask_about_color", cnd=ExactMatch("bird")), + Tr(dst="ask_about_breed", cnd=ExactMatch("dog")), + ], }, - "ask_about_color": {RESPONSE: Message("what color is it")}, + "ask_about_color": {RESPONSE: "what color is it"}, "ask_about_breed": { - RESPONSE: Message("what is this breed?"), - TRANSITIONS: { - "ask_about_breed": exact_match("pereat"), - "tell_fact_about_breed": exact_match("bulldog"), - "ask_about_training": exact_match("I don't know"), - }, + RESPONSE: "what is this breed?", + TRANSITIONS: [ + Tr(dst="ask_about_breed", cnd=ExactMatch("pereat")), + Tr(dst="tell_fact_about_breed", cnd=ExactMatch("bulldog")), + Tr(dst="ask_about_training", cnd=ExactMatch("I don't know")), + ], }, "tell_fact_about_breed": { - RESPONSE: Message("Bulldogs appeared in England as specialized bull-baiting dogs. "), + RESPONSE: "Bulldogs appeared in England as specialized bull-baiting dogs. ", }, - "ask_about_training": {RESPONSE: Message("Do you train your dog? ")}, + "ask_about_training": {RESPONSE: "Do you train your dog? "}, }, "news": { "what_news": { - RESPONSE: Message("what kind of news do you prefer?"), - TRANSITIONS: { - "ask_about_science": exact_match("science"), - "ask_about_sport": exact_match("sport"), - }, + RESPONSE: "what kind of news do you prefer?", + TRANSITIONS: [ + Tr(dst="ask_about_science", cnd=ExactMatch("science")), + Tr(dst="ask_about_sport", cnd=ExactMatch("sport")), + ], }, "ask_about_science": { - RESPONSE: Message("i got news about science, do you want to hear?"), - TRANSITIONS: { - "science_news": exact_match("yes"), - ("small_talk", "ask_some_questions"): exact_match("let's change the topic"), - }, + RESPONSE: "i got news about science, do you want to hear?", + TRANSITIONS: [ + Tr(dst="science_news", cnd=ExactMatch("yes")), + Tr(dst=("small_talk", "ask_some_questions"), cnd=ExactMatch("let's change the topic")), + ], }, "science_news": { - RESPONSE: Message("This is science news"), - TRANSITIONS: { - "what_news": exact_match("ok"), - ("small_talk", "ask_some_questions"): exact_match("let's change the topic"), - }, + RESPONSE: "This is science news", + TRANSITIONS: [ + Tr(dst="what_news", cnd=ExactMatch("ok")), + Tr(dst=("small_talk", "ask_some_questions"), cnd=ExactMatch("let's change the topic")), + ], }, "ask_about_sport": { - RESPONSE: Message("i got news about sport, do you want to hear?"), - TRANSITIONS: { - "sport_news": exact_match("yes"), - ("small_talk", "ask_some_questions"): exact_match("let's change the topic"), - }, + RESPONSE: "i got news about sport, do you want to hear?", + TRANSITIONS: [ + Tr(dst="sport_news", cnd=ExactMatch("yes")), + Tr(dst=("small_talk", "ask_some_questions"), cnd=ExactMatch("let's change the topic")), + ], }, "sport_news": { - RESPONSE: Message("This is sport news"), - TRANSITIONS: { - "what_news": exact_match("ok"), - ("small_talk", "ask_some_questions"): exact_match("let's change the topic"), - }, + RESPONSE: "This is sport news", + TRANSITIONS: [ + Tr(dst="what_news", cnd=ExactMatch("ok")), + Tr(dst=("small_talk", "ask_some_questions"), cnd=ExactMatch("let's change the topic")), + ], }, }, "small_talk": { "ask_some_questions": { - RESPONSE: Message("how are you"), - TRANSITIONS: { - "ask_talk_about": exact_match("fine"), - ("animals", "like_animals"): exact_match("let's talk about animals"), - ("news", "what_news"): exact_match("let's talk about news"), - }, + RESPONSE: "how are you", + TRANSITIONS: [ + Tr(dst="ask_talk_about", cnd=ExactMatch("fine")), + Tr(dst=("animals", "like_animals"), cnd=ExactMatch("let's talk about animals")), + Tr(dst=("news", "what_news"), cnd=ExactMatch("let's talk about news")), + ], }, "ask_talk_about": { - RESPONSE: Message("what do you want to talk about"), - TRANSITIONS: { - ("animals", "like_animals"): exact_match("dog"), - ("news", "what_news"): exact_match("let's talk about news"), - }, + RESPONSE: "what do you want to talk about", + TRANSITIONS: [ + Tr(dst=("animals", "like_animals"), cnd=ExactMatch("dog")), + Tr(dst=("news", "what_news"), cnd=ExactMatch("let's talk about news")), + ], }, }, } @@ -179,7 +178,10 @@ "hi", "i like animals", "let's talk about animals", - ] + ], + "fallback": [ + "to start", + ], }, "animals": { "have_pets": ["yes"], diff --git a/chatsky/utils/turn_caching/__init__.py b/chatsky/utils/turn_caching/__init__.py deleted file mode 100644 index ed53579a7..000000000 --- a/chatsky/utils/turn_caching/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from .singleton_turn_caching import cache_clear, lru_cache, cache diff --git a/chatsky/utils/turn_caching/singleton_turn_caching.py b/chatsky/utils/turn_caching/singleton_turn_caching.py deleted file mode 100644 index 06ae53ff0..000000000 --- a/chatsky/utils/turn_caching/singleton_turn_caching.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Singleton Turn Caching ----------------------- -This module contains functions for caching function results on each dialog turn. -""" - -import functools -from typing import Callable, List, Optional - - -USED_CACHES: List[Callable] = list() -"""Cache singleton, it is common for all actors and pipelines in current environment.""" - - -def cache_clear(): - """ - Function for cache singleton clearing, it is called in the end of pipeline execution turn. - """ - for used_cache in USED_CACHES: - used_cache.cache_clear() - - -def lru_cache(maxsize: Optional[int] = None, typed: bool = False) -> Callable: - """ - Decorator function for caching function results in scripts. - Works like the standard :py:func:`~functools.lru_cache` function. - Caches are kept in a library-wide singleton and cleared in the end of each turn. - """ - - def decorator(func): - global USED_CACHES - - @functools.wraps(func) - @functools.lru_cache(maxsize=maxsize, typed=typed) - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - - USED_CACHES += [wrapper] - return wrapper - - return decorator - - -def cache(func): - """ - Decorator function for caching function results in scripts. - Works like the standard :py:func:`~functools.cache` function. - Caches are kept in a library-wide singleton and cleared in the end of each turn. - """ - return lru_cache(maxsize=None)(func) diff --git a/chatsky/utils/viewer/__init__.py b/chatsky/utils/viewer/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/chatsky/utils/viewer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/docs/source/conf.py b/docs/source/conf.py index 842829391..b8d1a3d4f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -151,6 +151,7 @@ "members": True, "undoc-members": False, "private-members": True, + "special-members": "__call__", "member-order": "bysource", "exclude-members": "_abc_impl, model_fields, model_computed_fields, model_config", } @@ -184,20 +185,22 @@ def setup(_): ], ), ("tutorials.slots", "Slots"), - ("tutorials.utils", "Utils"), ("tutorials.stats", "Stats"), ] ) regenerate_apiref( [ + ("chatsky.core.service", "Core.Service"), + ("chatsky.core", "Core"), + ("chatsky.conditions", "Conditions"), + ("chatsky.destinations", "Destinations"), + ("chatsky.responses", "Responses"), + ("chatsky.processing", "Processing"), ("chatsky.context_storages", "Context Storages"), ("chatsky.messengers", "Messenger Interfaces"), - ("chatsky.pipeline", "Pipeline"), - ("chatsky.script", "Script"), ("chatsky.slots", "Slots"), ("chatsky.stats", "Stats"), ("chatsky.utils.testing", "Testing Utils"), - ("chatsky.utils.turn_caching", "Caching"), ("chatsky.utils.db_benchmark", "DB Benchmark"), ("chatsky.utils.devel", "Development Utils"), ] diff --git a/docs/source/get_started.rst b/docs/source/get_started.rst index a314896b1..4a6c5e9b2 100644 --- a/docs/source/get_started.rst +++ b/docs/source/get_started.rst @@ -55,7 +55,7 @@ range of applications, such as social networks, call centers, websites, personal Chatsky has several important concepts: **Script**: First of all, to create a dialog agent it is necessary -to create a dialog :py:class:`~chatsky.script.core.script.Script`. +to create a dialog :py:class:`~chatsky.core.script.Script`. A dialog `script` is a dictionary, where keys correspond to different `flows`. A script can contain multiple scripts, which are flows too, what is needed in order to divide a dialog into sub-dialogs and process them separately. diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index 4ac755196..e4199a5d0 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -11,7 +11,7 @@ The Messengers section covers how to use the Telegram messenger with Chatsky. The Pipeline section teaches the basics of the pipeline concept, how to use pre- and postprocessors, asynchronous groups and services, custom messenger interfaces, and extra handlers and extensions. The Script section covers the basics of the script concept, including conditions, responses, transitions, -and serialization. It also includes tutorials on pre-response and pre-transitions processing. +and serialization. It also includes tutorials on pre-response and pre-transition processing. Finally, the Utils section covers the cache and LRU cache utilities in Chatsky. The main difference between Tutorials and Examples is that Tutorials typically show how to implement diff --git a/docs/source/user_guides.rst b/docs/source/user_guides.rst index 0b4dcb41d..6802a4b34 100644 --- a/docs/source/user_guides.rst +++ b/docs/source/user_guides.rst @@ -13,7 +13,7 @@ about current script execution. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``slot extraction`` guide demonstrates the slot extraction functionality -currently integrated in the library. ``Chatsky`` only provides basic building blocks for this task, +currently integrated in the library. Chatsky only provides basic building blocks for this task, which can be trivially extended to support any NLU engine or slot extraction model of your liking. diff --git a/docs/source/user_guides/basic_conceptions.rst b/docs/source/user_guides/basic_conceptions.rst index b1f7eb39e..1259b6fb0 100644 --- a/docs/source/user_guides/basic_conceptions.rst +++ b/docs/source/user_guides/basic_conceptions.rst @@ -59,16 +59,19 @@ and handle any other messages as exceptions. The pseudo-code for the said flow w .. code-block:: text + 1. User starts a conversation + 2. Respond with "Hi!" + If user writes "Hello!": - Respond with "Hi! Let's play ping-pong!" + 3. Respond with "Let's play ping-pong!" If user afterwards writes "Ping" or "ping" or "Ping!" or "ping!": - Respond with "Pong!" + 4. Respond with "Pong!" Repeat this behaviour If user writes something else: - Respond with "That was against the rules" - Go to responding with "Hi! Let's play ping-pong!" if user writes anything + 5. Respond with "That was against the rules" + Go to responding with "2" after user replies This leaves us with a single dialog flow in the dialog graph that we lay down below, with the annotations for each part of the graph available under the code snippet. @@ -79,52 +82,44 @@ Example flow & script .. code-block:: python :linenos: - from chatsky.pipeline import Pipeline - from chatsky.script import TRANSITIONS, RESPONSE, Message - import chatsky.script.conditions as cnd + from chatsky import Pipeline, TRANSITIONS, RESPONSE, Transition as Tr + import chatsky.conditions as cnd + import chatsky.destinations as dst ping_pong_script = { "greeting_flow": { "start_node": { - RESPONSE: Message(), # the response of the initial node is skipped - TRANSITIONS: { - ("greeting_flow", "greeting_node"): - cnd.exact_match("/start"), - }, + TRANSITIONS: [Tr(dst="greeting_node", cnd=cnd.ExactMatch("/start"))] + # start node handles the initial handshake (command /start) }, "greeting_node": { - RESPONSE: Message("Hi!"), - TRANSITIONS: { - ("ping_pong_flow", "game_start_node"): - cnd.exact_match("Hello!") - } + RESPONSE: "Hi!", + TRANSITIONS: [ + Tr( + dst=("ping_pong_flow", "game_start_node"), + cnd=cnd.ExactMatch("Hello!") + ) + ] }, "fallback_node": { - RESPONSE: fallback_response, - TRANSITIONS: { - ("greeting_flow", "greeting_node"): cnd.true(), - }, + RESPONSE: "That was against the rules", + TRANSITIONS: [Tr(dst="greeting_node")], + # this transition is unconditional }, }, "ping_pong_flow": { "game_start_node": { - RESPONSE: Message("Let's play ping-pong!"), - TRANSITIONS: { - ("ping_pong_flow", "response_node"): - cnd.exact_match("Ping!"), - }, + RESPONSE: "Let's play ping-pong!", + TRANSITIONS: [Tr(dst="response_node", cnd=cnd.ExactMatch("Ping!"))], }, "response_node": { - RESPONSE: Message("Pong!"), - TRANSITIONS: { - ("ping_pong_flow", "response_node"): - cnd.exact_match("Ping!"), - }, + RESPONSE: "Pong!", + TRANSITIONS: [Tr(dst=dst.Current(), cnd=cnd.ExactMatch("Ping!"))], }, }, } - pipeline = Pipeline.from_script( + pipeline = Pipeline( ping_pong_script, start_label=("greeting_flow", "start_node"), fallback_label=("greeting_flow", "fallback_node"), @@ -133,8 +128,26 @@ Example flow & script if __name__ == "__main__": pipeline.run() -The code snippet defines a script with a single dialogue flow that emulates a ping-pong game. -Likewise, if additional scenarios need to be covered, additional flow objects can be embedded into the same script object. +An example chat with this bot: + +.. code-block:: + + request: /start + response: text='Hi!' + request: Hello! + response: text='Let's play ping-pong!' + request: Ping! + response: text='Pong!' + request: Bye + response: text='That was against the rules' + +The order of request processing is, essentially: + +1. Obtain user request +2. Travel to the next node (chosen based on transitions of the current node) +3. Send the response of the new node + +Below is a breakdown of key features used in the example: * ``ping_pong_script``: The dialog **script** mentioned above is a dictionary that has one or more dialog flows as its values. @@ -148,12 +161,11 @@ Likewise, if additional scenarios need to be covered, additional flow objects ca * The ``RESPONSE`` field specifies the response that the dialog agent gives to the user in the current turn. * The ``TRANSITIONS`` field specifies the edges of the dialog graph that link the dialog states. - This is a dictionary that maps labels of other nodes to conditions, i.e. callback functions that - return `True` or `False`. These conditions determine whether respective nodes can be visited - in the next turn. - In the example script, we use standard transitions: ``exact_match`` requires the user request to - fully match the provided text, while ``true`` always allows a transition. However, passing custom - callbacks that implement arbitrary logic is also an option. + This is a list of ``Transition`` instances. They specify the destination node of the potential transition + and a condition for the transition to be valid. + In the example script, we use build-in functions: ``ExactMatch`` requires the user request to + fully match the provided text, while ``Current`` makes a transition to the current node. + However, passing custom callbacks that implement arbitrary logic is also an option. * ``start_node`` is the initial node, which contains an empty response and only transfers user to another node according to the first message user sends. @@ -173,7 +185,7 @@ Likewise, if additional scenarios need to be covered, additional flow objects ca It is also capable of executing custom actions that you want to run on every turn of the conversation. The pipeline can be initialized with a script, and with labels of two nodes: the entrypoint of the graph, aka the 'start node', and the 'fallback node' - (if not provided it defaults to the same node as 'start node'). + (if not provided it defaults to 'start node'). .. note:: @@ -187,15 +199,15 @@ Processing Definition The topic of this section is explained in greater detail in the following tutorials: * `Pre-response processing <../tutorials/tutorials.script.core.7_pre_response_processing.html>`_ - * `Pre-transitions processing <../tutorials/tutorials.script.core.9_pre_transitions_processing.html>`_ + * `Pre-transition processing <../tutorials/tutorials.script.core.9_pre_transition_processing.html>`_ * `Pipeline processors <../tutorials/tutorials.pipeline.2_pre_and_post_processors.html>`_ Processing user requests and extracting additional parameters is a crucial part of building a conversational bot. Chatsky allows you to define how user requests will be processed to extract additional parameters. This is done by passing callbacks to a special ``PROCESSING`` fields in a Node dict. -* User input can be altered with ``PRE_RESPONSE_PROCESSING`` and will happen **before** response generation. See `tutorial on pre-response processing`_. -* Node response can be modified with ``PRE_TRANSITIONS_PROCESSING`` and will happen **after** response generation but **before** transition to the next node. See `tutorial on pre-transition processing`_. +* ``PRE_RESPONSE`` will happen **after** a transition has been made but **before** response generation. See `tutorial on pre-response processing`_. +* ``PRE_TRANSITION`` will happen **after** obtaining user request but **before** transition to the next node. See `tutorial on pre-transition processing`_. Depending on the requirements of your bot and the dialog goal, you may need to interact with external databases or APIs to retrieve data. For instance, if a user wants to know a schedule, you may need to access a database and extract parameters such as date and location. @@ -203,15 +215,17 @@ For instance, if a user wants to know a schedule, you may need to access a datab .. code-block:: python import requests + from chatsky import BaseProcessing, PRE_TRANSITION ... - def use_api_processing(ctx: Context, _: Pipeline): - # save to the context field for custom info - ctx.misc["api_call_results"] = requests.get("http://schedule.api/day1").json() + class UseAPI(BaseProcessing): + async def call(self, ctx): + # save to the context field for custom info + ctx.misc["api_call_results"] = requests.get("http://schedule.api/day1").json() ... node = { RESPONSE: ... TRANSITIONS: ... - PRE_TRANSITIONS_PROCESSING: {"use_api": use_api_processing} + PRE_TRANSITION: {"use_api": UseAPI()} } .. note:: @@ -223,25 +237,28 @@ For instance, if a user wants to know a schedule, you may need to access a datab If you retrieve data from the database or API, it's important to validate it to ensure it meets expectations. -Since Chatsky extensively leverages pydantic, you can resort to the validation tools of this feature-rich library. -For instance, given that each processing routine is a callback, you can use tools like pydantic's `validate_call` -to ensure that the returned values match the function signature. -Error handling logic can also be incorporated into these callbacks. - Generating a bot Response ========================= -Generating a bot response involves creating a text or multimedia response that will be delivered to the user. Response is defined in the ``RESPONSE`` section of each node and should be either a ``Message`` object, that can contain text, images, audios, attachments, etc., or a callback that returns a ``Message``. The latter allows you to customize the response based on the specific scenario and user input. +.. note:: + + ``Message`` object can be instantiated from a string (filling its ``text`` field). + We've used this feature for ``RESPONSE`` and will use it now. + .. code-block:: python - def sample_response(ctx: Context, _: Pipeline) -> Message: - if ctx.misc["user"] == 'vegan': - return Message("Here is a list of vegan cafes.") - return Message("Here is a list of cafes.") + class MyResponse(BaseResponse): + async def call(self, ctx): + if ctx.misc["user"] == 'vegan': + return "Here is a list of vegan cafes." + return "Here is a list of cafes." + + +For more information on responses, see the `tutorial on response functions`_. Handling Fallbacks ================== @@ -258,21 +275,19 @@ This ensures a smoother user experience even when the bot encounters unexpected .. code-block:: python - def fallback_response(ctx: Context, _: Pipeline) -> Message: + class MyResponse(BaseResponse): """ Generate a special fallback response depending on the situation. """ - if ctx.last_request is not None: - if ctx.last_request.text != "/start" and ctx.last_label is None: - # an empty last_label indicates start_node - return Message("You should've started the dialog with '/start'") + async def call(self, ctx): + if ctx.last_label == ctx.pipeline.start_label and ctx.last_request.text != "/start": + # start_label can be obtained from the pipeline instance stored inside context + return "You should've started the dialog with '/start'" else: - return Message( - text=f"That was against the rules!\n" - f"You should've written 'Ping', not '{ctx.last_request.text}'!" + return ( + f"That was against the rules!\n" + f"You should've written 'Ping', not '{ctx.last_request.text}'!" ) - else: - raise RuntimeError("Error occurred: last request is None!") Testing and Debugging ~~~~~~~~~~~~~~~~~~~~~ @@ -351,10 +366,10 @@ that you may have in your project, using Python docstrings. .. code-block:: python - def fav_kitchen_response(ctx: Context, _: Pipeline) -> Message: + class FavCuisineResponse(BaseResponse): """ This function returns a user-targeted response depending on the value - of the 'kitchen preference' slot. + of the 'cuisine preference' slot. """ ... @@ -380,8 +395,8 @@ Further reading * `Tutorial on conditions <../tutorials/tutorials.script.core.2_conditions.html>`_ * `Tutorial on response functions <../tutorials/tutorials.script.core.3_responses.html>`_ * `Tutorial on pre-response processing <../tutorials/tutorials.script.core.7_pre_response_processing.html>`_ -* `Tutorial on pre-transition processing <../tutorials/tutorials.script.core.9_pre_transitions_processing.html>`_ +* `Tutorial on pre-transition processing <../tutorials/tutorials.script.core.9_pre_transition_processing.html>`_ * `Guide on Context <../user_guides/context_guide.html>`_ -* `Tutorial on global transitions <../tutorials/tutorials.script.core.5_global_transitions.html>`_ +* `Tutorial on global and local nodes <../tutorials/tutorials.script.core.5_global_local.html>`_ * `Tutorial on context serialization <../tutorials/tutorials.script.core.6_context_serialization.html>`_ * `Tutorial on script MISC <../tutorials/tutorials.script.core.8_misc.html>`_ diff --git a/docs/source/user_guides/context_guide.rst b/docs/source/user_guides/context_guide.rst index d552a2efa..5c57edbd3 100644 --- a/docs/source/user_guides/context_guide.rst +++ b/docs/source/user_guides/context_guide.rst @@ -32,22 +32,27 @@ Let's consider some of the built-in callback instances to see how the context ca .. code-block:: python :linenos: - pattern = re.compile("[a-zA-Z]+") + class Regexp(BaseCondition): + pattern: str - def regexp_condition_handler(ctx: Context, pipeline: Pipeline) -> bool: - # retrieve the current request - request = ctx.last_request - if request.text is None: - return False - return bool(pattern.search(request.text)) + @cached_property + def re_object(self) -> Pattern: + return re.compile(self.pattern) -The code above is a condition function (see the `basic guide <./basic_conceptions.rst>`__) + async def call(self, ctx: Context) -> bool: + request = ctx.last_request + if request.text is None: + return False + return bool(self.re_object.search(request.text)) + +The code above is a condition function (see the `conditions tutorial <../tutorials/tutorials.script.core.2_conditions.py>`__) that belongs to the ``TRANSITIONS`` section of the script and returns `True` or `False` depending on whether the current user request matches the given pattern. + As can be seen from the code block, the current -request (``last_request``) can be easily retrieved as one of the attributes of the ``Context`` object. +request (``last_request``) can be retrieved as one of the attributes of the ``Context`` object. Likewise, the ``last_response`` (bot's current reply) or the ``last_label`` -(the name of the currently visited node) attributes can be used in the same manner. +(the name of the current node) attributes can be used in the same manner. Another common use case is leveraging the ``misc`` field (see below for a detailed description): pipeline functions or ``PROCESSING`` callbacks can write arbitrary values to the misc field, @@ -59,18 +64,17 @@ making those available for other context-dependent functions. import urllib.request import urllib.error - def ping_example_com( - ctx: Context, *_, **__ - ): - try: - with urllib.request.urlopen("https://example.com/") as webpage: - web_content = webpage.read().decode( - webpage.headers.get_content_charset() - ) - result = "Example Domain" in web_content - except urllib.error.URLError: - result = False - ctx.misc["can_ping_example_com"] = result + class PingExample(BaseProcessing): + async def call(self, ctx): + try: + with urllib.request.urlopen("https://example.com/") as webpage: + web_content = webpage.read().decode( + webpage.headers.get_content_charset() + ) + result = "Example Domain" in web_content + except urllib.error.URLError: + result = False + ctx.misc["can_ping_example_com"] = result .. todo: link to the user defined functions tutorial @@ -84,7 +88,7 @@ API This sections describes the API of the ``Context`` class. For more information, such as method signatures, see -`API reference <../apiref/chatsky.script.core.context.html#chatsky.script.core.context.Context>`__. +`API reference <../apiref/chatsky.core.context.html#chatsky.core.context.Context>`__. Attributes ========== @@ -111,6 +115,8 @@ Attributes * **framework_data**: This attribute is used for storing custom data required for pipeline execution. It is meant to be used by the framework only. Accessing it may result in pipeline breakage. + But there are some methods that provide access to specific fields of framework data. + These methods are described in the next section. Methods ======= @@ -124,58 +130,40 @@ The methods of the ``Context`` class can be divided into two categories: Public methods ^^^^^^^^^^^^^^ -* **last_request**: Return the last request of the context, or `None` if the ``requests`` field is empty. - - Note that a request is added right after the context is created/retrieved from db, - so an empty ``requests`` field usually indicates an issue with the messenger interface. +* **last_request**: Return the last request of the context. * **last_response**: Return the last response of the context, or `None` if the ``responses`` field is empty. Responses are added at the end of each turn, so an empty ``response`` field is something you should definitely consider. -* **last_label**: Return the last label of the context, or `None` if the ``labels`` field is empty. - Last label is always the name of the current node but not vice versa: - - Since ``start_label`` is not added to the ``labels`` field, - empty ``labels`` usually indicates that the current node is the `start_node`. - After a transition is made from the `start_node` - the label of that transition is added to the field. +* **last_label**: Return the last node label of the context (i.e. name of the current node). * **clear**: Clear all items from context fields, optionally keeping the data from `hold_last_n_indices` turns. You can specify which fields to clear using the `field_names` parameter. This method is designed for cases when contexts are shared over high latency networks. -.. note:: - - See the `preprocessing tutorial <../tutorials/tutorials.script.core.7_pre_response_processing.py>`__. +* **current_node**: Return the current node of the context. + Use this property to access properties of the current node. + You can safely modify properties of this. The changes will be reflected in + bot behaviour during this turn, bot are not permanent (the node stored inside the script is not changed). -Private methods -^^^^^^^^^^^^^^^ - -* **set_last_response, set_last_request**: These methods allow you to set the last response or request for the current context. - This functionality can prove useful if you want to create a middleware component that overrides the pipeline functionality. + .. note:: -* **add_request**: Add a request to the context. - It updates the `requests` dictionary. This method is called by the `Pipeline` component - before any of the `pipeline services <../tutorials/tutorials.pipeline.3_pipeline_dict_with_services_basic.py>`__ are executed, - including `Actor <../apiref/chatsky.pipeline.pipeline.actor.html>`__. + See the `preprocessing tutorial <../tutorials/tutorials.script.core.7_pre_response_processing.py>`__. -* **add_response**: Add a response to the context. - It updates the `responses` dictionary. This function is run by the `Actor <../apiref/chatsky.pipeline.pipeline.actor.html>`__ pipeline component at the end of the turn, after it has run - the `PRE_RESPONSE_PROCESSING <../tutorials/tutorials.script.core.7_pre_response_processing.py>`__ functions. +* **pipeline**: Return ``Pipeline`` object that is used to process this context. + This can be used to get ``Script``, ``start_label`` or ``fallback_label``. - To be more precise, this method is called between the ``CREATE_RESPONSE`` and ``FINISH_TURN`` stages. - For more information about stages, see `ActorStages <../apiref/chatsky.script.core.types.html#chatsky.script.core.types.ActorStage>`__. - -* **add_label**: Add a label to the context. - It updates the `labels` field. This method is called by the `Actor <../apiref/chatsky.pipeline.pipeline.actor.html>`_ component when transition conditions - have been resolved, and when `PRE_TRANSITIONS_PROCESSING <../tutorials/tutorials.script.core.9_pre_transitions_processing.py>`__ callbacks have been run. +Private methods +^^^^^^^^^^^^^^^ - To be more precise, this method is called between the ``GET_NEXT_NODE`` and ``REWRITE_NEXT_NODE`` stages. - For more information about stages, see `ActorStages <../apiref/chatsky.script.core.types.html#chatsky.script.core.types.ActorStage>`__. +These methods should not be used outside of the internal workings. -* **current_node**: Return the current node of the context. This is particularly useful for tracking the node during the conversation flow. - This method only returns a node inside ``PROCESSING`` callbacks yielding ``None`` in other contexts. +* **set_last_response** +* **set_last_request** +* **add_request** +* **add_response** +* **add_label** Context storages ~~~~~~~~~~~~~~~~ @@ -240,7 +228,6 @@ becomes as easy as calling the `model_dump_json` method: .. code-block:: python - context = Context() serialized_context = context.model_dump_json() Knowing that, you can easily extend Chatsky to work with storages like Memcache or web APIs of your liking. \ No newline at end of file diff --git a/docs/source/user_guides/optimization_guide.rst b/docs/source/user_guides/optimization_guide.rst index e71033614..5d4b3f625 100644 --- a/docs/source/user_guides/optimization_guide.rst +++ b/docs/source/user_guides/optimization_guide.rst @@ -93,10 +93,7 @@ that may help you improve the efficiency of your service. * Using caching for resource-consuming callbacks and actions may also prove to be a helpful strategy. In this manner, you can improve the computational efficiency of your pipeline, - while making very few changes to the code itself. Chatsky includes a caching mechanism - for response functions. However, the simplicity - of the Chatsky API makes it easy to integrate any custom caching solutions that you may come up with. - See the `Cache tutorial <../tutorials/tutorials.utils.1_cache.py>`__. + while making very few changes to the code itself. * Finally, be mindful about the use of computationally expensive algorithms, like NLU classifiers or LLM-based generative networks, since those require a great deal of time and resources diff --git a/docs/source/user_guides/slot_extraction.rst b/docs/source/user_guides/slot_extraction.rst index 8c3e7add0..61dcff117 100644 --- a/docs/source/user_guides/slot_extraction.rst +++ b/docs/source/user_guides/slot_extraction.rst @@ -53,10 +53,10 @@ full advantage of its predictions. import requests from chatsky.slots import FunctionSlot - from chatsky.script import Message + from chatsky import Message # we assume that there is a 'NER' service running on port 5000 - def extract_first_name(utterance: Message) -> str: + async def extract_first_name(utterance: Message) -> str: """Return the first entity of type B-PER (first name) found in the utterance.""" ner_request = requests.post( "http://localhost:5000/model", @@ -87,9 +87,9 @@ That slot is a root slot: it contains all other group and value slots. .. code-block:: python - from chatsky.pipeline import Pipeline + from chatsky import Pipeline - pipeline = Pipeline.from_script(..., slots=profile_slot) + pipeline = Pipeline(..., slots=profile_slot) Slot names ========== @@ -113,30 +113,30 @@ In this example ``name_slot`` would be accessible by the "profile.name" name. Using slots =========== -Slots can be extracted at the ``PRE_TRANSITIONS_PROCESSING`` stage -using the `extract <../apiref/chatsky.slots.processing.html#chatsky.slots.processing.extract>`_ +Slots can be extracted at the ``PRE_TRANSITION`` stage +using the `Extract <../apiref/chatsky.processing.slots.html#chatsky.processing.slots.Extract>`_ function from the `processing` submodule. You can pass any number of names of the slots that you want to extract to this function. .. code-block:: python - from chatsky.slots.processing import extract + from chatsky import proc - PRE_TRANSITIONS_PROCESSING: {"extract_first_name": extract("name", "email")} + PRE_TRANSITION: {"extract_first_name": proc.Extract("name", "email")} The `conditions` submodule provides a function for checking if specific slots have been extracted. .. code-block:: python - from chatsky.slots.conditions import slots_extracted + from chatsky import cnd - TRANSITIONS: {"all_information": slots_extracted("name", "email", mode="all")} - TRANSITIONS: {"partial_information": slots_extracted("name", "email", mode="any")} + TRANSITIONS: [Tr(dst="all_information", cnd=cnd.SlotsExtracted("name", "email", mode="all"))] + TRANSITIONS: [Tr(dst="partial_information", cnd=cnd.SlotsExtracted("name", "email", mode="any"))] .. note:: You can combine ``slots_extracted`` with the - `negation <../apiref/chatsky.script.conditions.std_conditions.html#chatsky.script.conditions.std_conditions.negation>`_ + `Negation <../apiref/chatsky.conditions.standard.html#chatsky.conditions.standard.Negation>`_ condition to make a transition to an extractor node if a slot has not been extracted yet. Both `processing` and `response` submodules provide functions for filling templates with @@ -145,14 +145,13 @@ Choose whichever one you like, there's not much difference between them at the m .. code-block:: python - from chatsky.slots.processing import fill_template - from chatsky.slots.response import filled_template + from chatsky import proc, rsp - PRE_RESPONSE_PROCESSING: {"fill_response_slots": slot_procs.fill_template()} - RESPONSE: Message(text="Your first name: {name}") + PRE_RESPONSE: {"fill_response_slots": proc.FillTemplate()} + RESPONSE: "Your first name: {name}" - RESPONSE: filled_template(Message(text="Your first name: {name}")) + RESPONSE: rsp.FilledTemplate("Your first name: {name}") Some real examples of scripts utilizing slot extraction can be found in the `tutorials section <../tutorials/tutorials.slots.1_basic_example.html>`_. diff --git a/pyproject.toml b/pyproject.toml index 2aa8025a0..cbde2143f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,8 +222,7 @@ concurrency = [ [tool.coverage.report] # Regexes for lines to exclude from consideration exclude_also = [ - # Don't complain if tests don't cover raising errors: - "raise .*", - # Don't complain if tests don't cover error handling: - "except .*", + "if TYPE_CHECKING:", + "raise NotImplementedError", + "raise RuntimeError", ] diff --git a/tests/conftest.py b/tests/conftest.py index 85ba92404..dad455b74 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +import logging + import pytest @@ -42,3 +44,27 @@ def pytest_addoption(parser): " If not passed, every test is permitted to skip." " Pass `none` to disallow any test from skipping.", ) + + +@pytest.fixture +def log_event_catcher(): + """ + Return a function that takes a logger and returns a list. + Logger will put `LogRecord` objects into the list. + + Optionally, the function accepts `level` to set minimum log level. + """ + + def inner(logger, *, level=logging.DEBUG): + logs = [] + + class Handler(logging.Handler): + def emit(self, record) -> bool: + logs.append(record) + return True + + logger.addHandler(Handler()) + logger.setLevel(level) + return logs + + return inner diff --git a/tests/context_storages/conftest.py b/tests/context_storages/conftest.py index b6f6dab87..b2739bfe5 100644 --- a/tests/context_storages/conftest.py +++ b/tests/context_storages/conftest.py @@ -1,6 +1,6 @@ import uuid -from chatsky.script import Context +from chatsky.core import Context import pytest diff --git a/tests/context_storages/test_dbs.py b/tests/context_storages/test_dbs.py index 43820e68f..db94446a6 100644 --- a/tests/context_storages/test_dbs.py +++ b/tests/context_storages/test_dbs.py @@ -20,7 +20,7 @@ context_storage_factory, ) -from chatsky.script import Context +from chatsky.core import Context, Pipeline from chatsky.utils.testing.cleanup_db import ( delete_shelve, delete_json, @@ -31,12 +31,8 @@ delete_ydb, ) -from tests.test_utils import get_path_from_tests_to_current_dir -from chatsky.pipeline import Pipeline from chatsky.utils.testing import check_happy_path, TOY_SCRIPT_KWARGS, HAPPY_PATH -dot_path_to_addon = get_path_from_tests_to_current_dir(__file__, separator=".") - def ping_localhost(port: int, timeout=60): try: diff --git a/tests/script/__init__.py b/tests/core/__init__.py similarity index 100% rename from tests/script/__init__.py rename to tests/core/__init__.py diff --git a/tests/core/conftest.py b/tests/core/conftest.py new file mode 100644 index 000000000..465404d6d --- /dev/null +++ b/tests/core/conftest.py @@ -0,0 +1,40 @@ +import pytest + +from chatsky.core import Pipeline +from chatsky.core import Context + + +@pytest.fixture +def pipeline(): + return Pipeline( + script={"flow": {"node1": {}, "node2": {}, "node3": {}}, "service": {"start": {}, "fallback": {}}}, + start_label=("service", "start"), + fallback_label=("service", "fallback"), + ) + + +@pytest.fixture +def context_factory(pipeline): + def _context_factory(forbidden_fields=None, add_start_label=True): + if add_start_label: + ctx = Context.init(("service", "start")) + else: + ctx = Context() + ctx.framework_data.pipeline = pipeline + if forbidden_fields is not None: + + class Forbidden: + def __init__(self, name): + self.name = name + + class ForbiddenError(Exception): + pass + + def __getattr__(self, item): + raise self.ForbiddenError(f"{self.name!r} is forbidden") + + for forbidden_field in forbidden_fields: + ctx.__setattr__(forbidden_field, Forbidden(forbidden_field)) + return ctx + + return _context_factory diff --git a/tests/core/test_actor.py b/tests/core/test_actor.py new file mode 100644 index 000000000..d3c6d1318 --- /dev/null +++ b/tests/core/test_actor.py @@ -0,0 +1,210 @@ +import asyncio + +import pytest + +from chatsky.core import BaseProcessing, BaseResponse, Pipeline +from chatsky.core.node_label import AbsoluteNodeLabel +from chatsky.core.service.actor import Actor, logger +from chatsky.core.message import Message, MessageInitTypes +from chatsky.core.context import Context +from chatsky.core.script import Script +from chatsky.core import RESPONSE, TRANSITIONS, PRE_TRANSITION, PRE_RESPONSE + + +class TestRequestProcessing: + async def test_normal_execution(self): + script = Script.model_validate( + { + "flow": { + "node1": {RESPONSE: "node1", TRANSITIONS: [{"dst": "node2"}]}, + "node2": {RESPONSE: "node2", TRANSITIONS: [{"dst": "node3"}]}, + "node3": {RESPONSE: "node3"}, + "fallback": {RESPONSE: "fallback"}, + } + } + ) + + ctx = Context.init(start_label=("flow", "node1")) + actor = Actor() + ctx.framework_data.pipeline = Pipeline( + parallelize_processing=True, + script=script, + fallback_label=AbsoluteNodeLabel(flow_name="flow", node_name="fallback"), + start_label=("flow", "node1"), + ) + + await actor(ctx, ctx.framework_data.pipeline) + + assert ctx.labels == { + 0: AbsoluteNodeLabel(flow_name="flow", node_name="node1"), + 1: AbsoluteNodeLabel(flow_name="flow", node_name="node2"), + } + assert ctx.responses == {1: Message(text="node2")} + + async def test_fallback_node(self): + script = Script.model_validate({"flow": {"node": {}, "fallback": {RESPONSE: "fallback"}}}) + + ctx = Context.init(start_label=("flow", "node")) + actor = Actor() + ctx.framework_data.pipeline = Pipeline( + parallelize_processing=True, + script=script, + fallback_label=AbsoluteNodeLabel(flow_name="flow", node_name="fallback"), + start_label=("flow", "node"), + ) + + await actor(ctx, ctx.framework_data.pipeline) + + assert ctx.labels == { + 0: AbsoluteNodeLabel(flow_name="flow", node_name="node"), + 1: AbsoluteNodeLabel(flow_name="flow", node_name="fallback"), + } + assert ctx.responses == {1: Message(text="fallback")} + + @pytest.mark.parametrize( + "default_priority,result", + [ + (1, "node3"), + (2, "node2"), + (3, "node2"), + ], + ) + async def test_default_priority(self, default_priority, result): + script = Script.model_validate( + { + "flow": { + "node1": {TRANSITIONS: [{"dst": "node2"}, {"dst": "node3", "priority": 2}]}, + "node2": {}, + "node3": {}, + "fallback": {}, + } + } + ) + + ctx = Context.init(start_label=("flow", "node1")) + actor = Actor() + ctx.framework_data.pipeline = Pipeline( + parallelize_processing=True, + script=script, + fallback_label=AbsoluteNodeLabel(flow_name="flow", node_name="fallback"), + default_priority=default_priority, + start_label=("flow", "node1"), + ) + + await actor(ctx, ctx.framework_data.pipeline) + assert ctx.last_label.node_name == result + + async def test_transition_exception_handling(self, log_event_catcher): + log_list = log_event_catcher(logger, level="ERROR") + + class MyProcessing(BaseProcessing): + async def call(self, ctx: Context) -> None: + ctx.framework_data.current_node = None + + script = Script.model_validate({"flow": {"node": {PRE_TRANSITION: {"": MyProcessing()}}, "fallback": {}}}) + + ctx = Context.init(start_label=("flow", "node")) + actor = Actor() + ctx.framework_data.pipeline = Pipeline( + parallelize_processing=True, + script=script, + fallback_label=AbsoluteNodeLabel(flow_name="flow", node_name="fallback"), + start_label=("flow", "node"), + ) + + await actor(ctx, ctx.framework_data.pipeline) + + assert ctx.last_label.node_name == "fallback" + assert log_list[0].msg == "Exception occurred during transition processing." + assert str(log_list[0].exc_info[1]) == "Current node is not set." + + async def test_empty_response(self, log_event_catcher): + log_list = log_event_catcher(logger, level="DEBUG") + + script = Script.model_validate({"flow": {"node": {}}}) + + ctx = Context.init(start_label=("flow", "node")) + actor = Actor() + ctx.framework_data.pipeline = Pipeline( + parallelize_processing=True, + script=script, + fallback_label=AbsoluteNodeLabel(flow_name="flow", node_name="node"), + start_label=("flow", "node"), + ) + + await actor(ctx, ctx.framework_data.pipeline) + + assert ctx.responses == {1: Message()} + assert log_list[-1].msg == "Node has empty response." + + async def test_bad_response(self, log_event_catcher): + log_list = log_event_catcher(logger, level="DEBUG") + + class MyResponse(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + return None + + script = Script.model_validate({"flow": {"node": {RESPONSE: MyResponse()}}}) + + ctx = Context.init(start_label=("flow", "node")) + actor = Actor() + ctx.framework_data.pipeline = Pipeline( + parallelize_processing=True, + script=script, + fallback_label=AbsoluteNodeLabel(flow_name="flow", node_name="node"), + start_label=("flow", "node"), + ) + + await actor(ctx, ctx.framework_data.pipeline) + + assert ctx.responses == {1: Message()} + assert log_list[-1].msg == "Response was not produced." + + async def test_response_exception_handling(self, log_event_catcher): + log_list = log_event_catcher(logger, level="ERROR") + + class MyProcessing(BaseProcessing): + async def call(self, ctx: Context) -> None: + ctx.framework_data.current_node = None + + script = Script.model_validate({"flow": {"node": {PRE_RESPONSE: {"": MyProcessing()}}}}) + + ctx = Context.init(start_label=("flow", "node")) + actor = Actor() + ctx.framework_data.pipeline = Pipeline( + parallelize_processing=True, + script=script, + fallback_label=AbsoluteNodeLabel(flow_name="flow", node_name="node"), + start_label=("flow", "node"), + ) + + await actor(ctx, ctx.framework_data.pipeline) + + assert ctx.responses == {1: Message()} + assert log_list[0].msg == "Exception occurred during response processing." + assert str(log_list[0].exc_info[1]) == "Current node is not set." + + +async def test_pre_processing(): + contested_resource = {} + + class Proc1(BaseProcessing): + async def call(self, ctx: Context) -> None: + await asyncio.sleep(0) + contested_resource[""] = 1 + + class Proc2(BaseProcessing): + async def call(self, ctx: Context) -> None: + contested_resource[""] = 2 + + procs = {"1": Proc1(), "2": Proc2()} + + ctx = Context.init(start_label=("flow", "node")) + + ctx.framework_data.pipeline = Pipeline(parallelize_processing=True, script={"": {"": {}}}, start_label=("", "")) + await Actor._run_processing(procs, ctx) + assert contested_resource[""] == 1 + + ctx.framework_data.pipeline = Pipeline(parallelize_processing=False, script={"": {"": {}}}, start_label=("", "")) + await Actor._run_processing(procs, ctx) + assert contested_resource[""] == 2 diff --git a/tests/core/test_conditions.py b/tests/core/test_conditions.py new file mode 100644 index 000000000..4d1a3f33f --- /dev/null +++ b/tests/core/test_conditions.py @@ -0,0 +1,138 @@ +import pytest + +from chatsky.core import BaseCondition +from chatsky.core.message import Message, CallbackQuery +import chatsky.conditions as cnd + + +class FaultyCondition(BaseCondition): + async def call(self, ctx) -> bool: + raise RuntimeError() + + +class SubclassMessage(Message): + additional_field: str + + +@pytest.fixture +def request_based_ctx(context_factory): + ctx = context_factory(forbidden_fields=("labels", "responses", "misc")) + ctx.add_request(Message(text="text", misc={"key": "value"})) + return ctx + + +@pytest.mark.parametrize( + "condition,result", + [ + (cnd.ExactMatch(Message(text="text", misc={"key": "value"})), True), + (cnd.ExactMatch(Message(text="text"), skip_none=True), True), + (cnd.ExactMatch(Message(text="text"), skip_none=False), False), + (cnd.ExactMatch("text", skip_none=True), True), + (cnd.ExactMatch(Message(text="")), False), + (cnd.ExactMatch(Message(text="text", misc={"key": None})), False), + (cnd.ExactMatch(Message(), skip_none=True), True), + (cnd.ExactMatch({}, skip_none=True), True), + (cnd.ExactMatch(SubclassMessage(text="text", misc={"key": "value"}, additional_field="")), False), + ], +) +async def test_exact_match(request_based_ctx, condition, result): + assert await condition(request_based_ctx) is result + + +@pytest.mark.parametrize( + "condition,result", + [ + (cnd.HasText("text"), True), + (cnd.HasText("te"), True), + (cnd.HasText("text1"), False), + ], +) +async def test_has_text(request_based_ctx, condition, result): + assert await condition(request_based_ctx) is result + + +@pytest.mark.parametrize( + "condition,result", + [ + (cnd.Regexp("t.*t"), True), + (cnd.Regexp("t.*t1"), False), + ], +) +async def test_regexp(request_based_ctx, condition, result): + assert await condition(request_based_ctx) is result + + +@pytest.mark.parametrize( + "condition,result", + [ + (cnd.Any(cnd.Regexp("t.*"), cnd.Regexp(".*t")), True), + (cnd.Any(FaultyCondition(), cnd.Regexp("t.*"), cnd.Regexp(".*t")), True), + (cnd.Any(FaultyCondition()), False), + (cnd.Any(cnd.Regexp("t.*"), cnd.Regexp(".*t1")), True), + (cnd.Any(cnd.Regexp("1t.*"), cnd.Regexp(".*t1")), False), + ], +) +async def test_any(request_based_ctx, condition, result): + assert await condition(request_based_ctx) is result + + +@pytest.mark.parametrize( + "condition,result", + [ + (cnd.All(cnd.Regexp("t.*"), cnd.Regexp(".*t")), True), + (cnd.All(FaultyCondition(), cnd.Regexp("t.*"), cnd.Regexp(".*t")), False), + (cnd.All(cnd.Regexp("t.*"), cnd.Regexp(".*t1")), False), + ], +) +async def test_all(request_based_ctx, condition, result): + assert await condition(request_based_ctx) is result + + +@pytest.mark.parametrize( + "condition,result", + [ + (cnd.Not(cnd.HasText("text")), False), + (cnd.Not(cnd.HasText("text1")), True), + (cnd.Not(FaultyCondition()), True), + ], +) +async def test_neg(request_based_ctx, condition, result): + assert await condition(request_based_ctx) is result + + +async def test_has_last_labels(context_factory): + ctx = context_factory(forbidden_fields=("requests", "responses", "misc")) + ctx.add_label(("flow", "node1")) + + assert await cnd.CheckLastLabels(flow_labels=["flow"])(ctx) is True + assert await cnd.CheckLastLabels(flow_labels=["flow1"])(ctx) is False + + assert await cnd.CheckLastLabels(labels=[("flow", "node1")])(ctx) is True + assert await cnd.CheckLastLabels(labels=[("flow", "node2")])(ctx) is False + + ctx.add_label(("service", "start")) + + assert await cnd.CheckLastLabels(flow_labels=["flow"])(ctx) is False + assert await cnd.CheckLastLabels(flow_labels=["flow"], last_n_indices=2)(ctx) is True + + assert await cnd.CheckLastLabels(labels=[("flow", "node1")])(ctx) is False + assert await cnd.CheckLastLabels(labels=[("flow", "node1")], last_n_indices=2)(ctx) is True + + +async def test_has_callback_query(context_factory): + ctx = context_factory(forbidden_fields=("labels", "responses", "misc")) + ctx.add_request( + Message(attachments=[CallbackQuery(query_string="text", extra="extra"), CallbackQuery(query_string="text1")]) + ) + + assert await cnd.HasCallbackQuery("text")(ctx) is True + assert await cnd.HasCallbackQuery("t")(ctx) is False + assert await cnd.HasCallbackQuery("text1")(ctx) is True + + +@pytest.mark.parametrize("cnd", [cnd.HasText(""), cnd.Regexp(""), cnd.HasCallbackQuery("")]) +async def test_empty_text(context_factory, cnd): + ctx = context_factory() + ctx.add_request(Message()) + + assert await cnd(ctx) is False diff --git a/tests/core/test_context.py b/tests/core/test_context.py new file mode 100644 index 000000000..1ca0e9842 --- /dev/null +++ b/tests/core/test_context.py @@ -0,0 +1,147 @@ +import pytest + +from chatsky.core.context import get_last_index, Context, ContextError +from chatsky.core.node_label import AbsoluteNodeLabel +from chatsky.core.message import Message, MessageInitTypes +from chatsky.core.script_function import BaseResponse, BaseProcessing +from chatsky.core.pipeline import Pipeline +from chatsky.core import RESPONSE, PRE_TRANSITION, PRE_RESPONSE + + +class TestGetLastIndex: + @pytest.mark.parametrize( + "dict,result", + [ + ({1: None, 5: None}, 5), + ({5: None, 1: None}, 5), + ], + ) + def test_normal(self, dict, result): + assert get_last_index(dict) == result + + def test_exception(self): + with pytest.raises(ValueError): + get_last_index({}) + + +def test_init(): + ctx1 = Context.init(AbsoluteNodeLabel(flow_name="flow", node_name="node")) + ctx2 = Context.init(AbsoluteNodeLabel(flow_name="flow", node_name="node")) + assert ctx1.labels == {0: AbsoluteNodeLabel(flow_name="flow", node_name="node")} + assert ctx1.requests == {} + assert ctx1.responses == {} + assert ctx1.id != ctx2.id + + ctx3 = Context.init(AbsoluteNodeLabel(flow_name="flow", node_name="node"), id="id") + assert ctx3.labels == {0: AbsoluteNodeLabel(flow_name="flow", node_name="node")} + assert ctx3.requests == {} + assert ctx3.responses == {} + assert ctx3.id == "id" + + +class TestLabels: + @pytest.fixture + def ctx(self, context_factory): + return context_factory(forbidden_fields=["requests", "responses"], add_start_label=False) + + def test_raises_on_empty_labels(self, ctx): + with pytest.raises(ContextError): + ctx.add_label(("flow", "node")) + + with pytest.raises(ContextError): + ctx.last_label + + def test_existing_labels(self, ctx): + ctx.labels = {5: AbsoluteNodeLabel.model_validate(("flow", "node1"))} + + assert ctx.last_label == AbsoluteNodeLabel(flow_name="flow", node_name="node1") + ctx.add_label(("flow", "node2")) + assert ctx.labels == { + 5: AbsoluteNodeLabel(flow_name="flow", node_name="node1"), + 6: AbsoluteNodeLabel(flow_name="flow", node_name="node2"), + } + assert ctx.last_label == AbsoluteNodeLabel(flow_name="flow", node_name="node2") + + +class TestRequests: + @pytest.fixture + def ctx(self, context_factory): + return context_factory(forbidden_fields=["labels", "responses"], add_start_label=False) + + def test_existing_requests(self, ctx): + ctx.requests = {5: Message(text="text1")} + assert ctx.last_request == Message(text="text1") + ctx.add_request("text2") + assert ctx.requests == {5: Message(text="text1"), 6: Message(text="text2")} + assert ctx.last_request == Message(text="text2") + + def test_empty_requests(self, ctx): + with pytest.raises(ContextError): + ctx.last_request + + ctx.add_request("text") + assert ctx.last_request == Message(text="text") + assert list(ctx.requests.keys()) == [1] + + +class TestResponses: + @pytest.fixture + def ctx(self, context_factory): + return context_factory(forbidden_fields=["labels", "requests"], add_start_label=False) + + def test_existing_responses(self, ctx): + ctx.responses = {5: Message(text="text1")} + assert ctx.last_response == Message(text="text1") + ctx.add_response("text2") + assert ctx.responses == {5: Message(text="text1"), 6: Message(text="text2")} + assert ctx.last_response == Message(text="text2") + + def test_empty_responses(self, ctx): + assert ctx.last_response is None + + ctx.add_response("text") + assert ctx.last_response == Message(text="text") + assert list(ctx.responses.keys()) == [1] + + +def test_last_items_on_init(): + ctx = Context.init(("flow", "node")) + + assert ctx.last_label == AbsoluteNodeLabel(flow_name="flow", node_name="node") + assert ctx.last_response is None + with pytest.raises(ContextError): + ctx.last_request + + +async def test_pipeline_available(): + class MyResponse(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + return ctx.pipeline.start_label.node_name + + pipeline = Pipeline(script={"flow": {"node": {RESPONSE: MyResponse()}}}, start_label=("flow", "node")) + ctx = await pipeline._run_pipeline(Message(text="")) + + assert ctx.last_response == Message(text="node") + + ctx.framework_data.pipeline = None + with pytest.raises(ContextError): + await MyResponse().call(ctx) + + +async def test_current_node_available(): + log = [] + + class MyProcessing(BaseProcessing): + async def call(self, ctx: Context) -> None: + log.append(ctx.current_node) + + pipeline = Pipeline( + script={"flow": {"node": {PRE_RESPONSE: {"": MyProcessing()}, PRE_TRANSITION: {"": MyProcessing()}}}}, + start_label=("flow", "node"), + ) + ctx = await pipeline._run_pipeline(Message(text="")) + assert len(log) == 2 + + ctx.framework_data.current_node = None + with pytest.raises(ContextError): + await MyProcessing().call(ctx) diff --git a/tests/core/test_destinations.py b/tests/core/test_destinations.py new file mode 100644 index 000000000..5126c71aa --- /dev/null +++ b/tests/core/test_destinations.py @@ -0,0 +1,96 @@ +import pytest +from pydantic import ValidationError + +import chatsky.destinations.standard as dst +from chatsky.core.node_label import AbsoluteNodeLabel + + +@pytest.fixture +def ctx(context_factory): + return context_factory(forbidden_fields=("requests", "responses", "misc")) + + +async def test_from_history(ctx): + assert ( + await dst.FromHistory(position=-1)(ctx) + == await dst.Current()(ctx) + == AbsoluteNodeLabel(flow_name="service", node_name="start") + ) + with pytest.raises(KeyError): + await dst.FromHistory(position=-2)(ctx) + + ctx.add_label(("flow", "node1")) + assert ( + await dst.FromHistory(position=-1)(ctx) + == await dst.Current()(ctx) + == AbsoluteNodeLabel(flow_name="flow", node_name="node1") + ) + assert ( + await dst.FromHistory(position=-2)(ctx) + == await dst.Previous()(ctx) + == AbsoluteNodeLabel(flow_name="service", node_name="start") + ) + with pytest.raises(KeyError): + await dst.FromHistory(position=-3)(ctx) + + ctx.add_label(("flow", "node2")) + assert await dst.Current()(ctx) == AbsoluteNodeLabel(flow_name="flow", node_name="node2") + assert await dst.FromHistory(position=-3)(ctx) == AbsoluteNodeLabel(flow_name="service", node_name="start") + + +async def test_start(ctx): + assert await dst.Start()(ctx) == AbsoluteNodeLabel(flow_name="service", node_name="start") + + +async def test_fallback(ctx): + assert await dst.Fallback()(ctx) == AbsoluteNodeLabel(flow_name="service", node_name="fallback") + + +class TestForwardBackward: + @pytest.mark.parametrize( + "node,inc,loop,result", + [ + (("flow", "node1"), True, False, ("flow", "node2")), + (("flow", "node1"), False, True, ("flow", "node3")), + (("flow", "node2"), True, False, ("flow", "node3")), + (("flow", "node2"), False, False, ("flow", "node1")), + (("flow", "node3"), True, True, ("flow", "node1")), + ], + ) + def test_get_next_node_in_flow(self, ctx, node, inc, loop, result): + assert dst.get_next_node_in_flow(node, ctx, increment=inc, loop=loop) == AbsoluteNodeLabel.model_validate( + result + ) + + @pytest.mark.parametrize( + "node,inc,loop", + [ + (("flow", "node1"), False, False), + (("flow", "node3"), True, False), + ], + ) + def test_loop_exception(self, ctx, node, inc, loop): + with pytest.raises(IndexError): + dst.get_next_node_in_flow(node, ctx, increment=inc, loop=loop) + + def test_non_existent_node_exception(self, ctx): + with pytest.raises(ValidationError): + dst.get_next_node_in_flow(("flow", "node4"), ctx) + + async def test_forward(self, ctx): + ctx.add_label(("flow", "node2")) + assert await dst.Forward()(ctx) == AbsoluteNodeLabel(flow_name="flow", node_name="node3") + + ctx.add_label(("flow", "node3")) + assert await dst.Forward(loop=True)(ctx) == AbsoluteNodeLabel(flow_name="flow", node_name="node1") + with pytest.raises(IndexError): + await dst.Forward(loop=False)(ctx) + + async def test_backward(self, ctx): + ctx.add_label(("flow", "node2")) + assert await dst.Backward()(ctx) == AbsoluteNodeLabel(flow_name="flow", node_name="node1") + + ctx.add_label(("flow", "node1")) + assert await dst.Backward(loop=True)(ctx) == AbsoluteNodeLabel(flow_name="flow", node_name="node3") + with pytest.raises(IndexError): + await dst.Backward(loop=False)(ctx) diff --git a/tests/script/core/test_message.py b/tests/core/test_message.py similarity index 96% rename from tests/script/core/test_message.py rename to tests/core/test_message.py index 9c9a01db1..64b2b0a86 100644 --- a/tests/script/core/test_message.py +++ b/tests/core/test_message.py @@ -10,7 +10,7 @@ from chatsky.messengers.common.interface import MessengerInterfaceWithAttachments from chatsky.messengers.console import CLIMessengerInterface -from chatsky.script.core.message import ( +from chatsky.core.message import ( Animation, Audio, CallbackQuery, @@ -125,6 +125,9 @@ async def test_getting_attachment_bytes(self, tmp_path): cached_bytes = document.cached_filename.read_bytes() assert document_bytes == cached_bytes + cached_bytes_via_get_bytes = await document.get_bytes(cli_iface) + assert document_bytes == cached_bytes_via_get_bytes + def test_missing_error(self): with pytest.raises(ValidationError) as e: _ = DataAttachment(source=HttpUrl("http://google.com"), id="123") diff --git a/tests/core/test_processing.py b/tests/core/test_processing.py new file mode 100644 index 000000000..0c3e7c509 --- /dev/null +++ b/tests/core/test_processing.py @@ -0,0 +1,24 @@ +from chatsky import proc, Context, BaseResponse, MessageInitTypes, Message +from chatsky.core.script import Node + + +async def test_modify_response(): + ctx = Context() + ctx.framework_data.current_node = Node() + + class MyModifiedResponse(proc.ModifyResponse): + async def modified_response(self, original_response: BaseResponse, ctx: Context) -> MessageInitTypes: + result = await original_response(ctx) + return Message(misc={"msg": result}) + + await MyModifiedResponse()(ctx) + + assert ctx.current_node.response is None + + ctx.framework_data.current_node = Node(response="hi") + + await MyModifiedResponse()(ctx) + + assert ctx.current_node.response.__class__.__name__ == "ModifiedResponse" + + assert await ctx.current_node.response(ctx) == Message(misc={"msg": Message("hi")}) diff --git a/tests/core/test_responses.py b/tests/core/test_responses.py new file mode 100644 index 000000000..fbf60c166 --- /dev/null +++ b/tests/core/test_responses.py @@ -0,0 +1,25 @@ +import random + +import pytest + +from chatsky.core import Message +from chatsky.responses import RandomChoice + + +@pytest.fixture +def ctx(context_factory): + return context_factory(forbidden_fields=("labels", "requests", "responses", "misc")) + + +async def test_random_choice(ctx): + random.seed(0) + + rsp = RandomChoice( + Message(text="1"), + Message(text="2"), + Message(text="3"), + ) + + assert (await rsp(ctx)).text == "2" + assert (await rsp(ctx)).text == "2" + assert (await rsp(ctx)).text == "1" diff --git a/tests/core/test_script.py b/tests/core/test_script.py new file mode 100644 index 000000000..0d565ddd1 --- /dev/null +++ b/tests/core/test_script.py @@ -0,0 +1,84 @@ +import pytest + +from chatsky.core import Transition as Tr, BaseProcessing, Context, AbsoluteNodeLabel +from chatsky.core.script import Node, Flow, Script + + +class MyProcessing(BaseProcessing): + value: str = "" + + async def call(self, ctx: Context) -> None: + return + + +@pytest.mark.parametrize( + "first,second,result", + [ + ( + Node(transitions=[Tr(dst="node1"), Tr(dst="node2")]), + Node(transitions=[Tr(dst="node3"), Tr(dst="node4")]), + Node(transitions=[Tr(dst="node3"), Tr(dst="node4"), Tr(dst="node1"), Tr(dst="node2")]), + ), + ( + Node(response="msg1"), + Node(response="msg2"), + Node(response="msg2"), + ), + ( + Node(response="msg1"), + Node(), + Node(response="msg1"), + ), + ( + Node( + pre_response={"key": MyProcessing(value="1")}, + pre_transition={"key": MyProcessing(value="3")}, + misc={"k1": "v1"}, + ), + Node(pre_response={"key": MyProcessing(value="2")}, pre_transition={}, misc={"k2": "v2"}), + Node( + pre_response={"key": MyProcessing(value="2")}, + pre_transition={"key": MyProcessing(value="3")}, + misc={"k1": "v1", "k2": "v2"}, + ), + ), + ], +) +def test_node_merge(first, second, result): + assert first.merge(second) == result + + +def test_flow_get_node(): + flow = Flow(node1=Node(response="text")) + + assert flow.get_node("node1") == Node(response="text") + assert flow.get_node("node2") is None + + +def test_script_get_methods(): + flow = Flow(node1=Node(response="text")) + script = Script(flow1=flow) + + assert script.get_flow("flow1") == flow + assert script.get_flow("flow2") is None + + assert script.get_node(AbsoluteNodeLabel(flow_name="flow1", node_name="node1")) == Node(response="text") + assert script.get_node(AbsoluteNodeLabel(flow_name="flow1", node_name="node2")) is None + assert script.get_node(AbsoluteNodeLabel(flow_name="flow2", node_name="node1")) is None + + +def test_get_inherited_node(): + global_node = Node(misc={"k1": "g1", "k2": "g2", "k3": "g3"}) + local_node = Node(misc={"k2": "l1", "k3": "l2", "k4": "l3"}) + node = Node(misc={"k3": "n1", "k4": "n2", "k5": "n3"}) + script = Script.model_validate({"global": global_node, "flow": {"local": local_node, "node": node}}) + + assert script.get_inherited_node(AbsoluteNodeLabel(flow_name="", node_name="")) is None + assert script.get_inherited_node(AbsoluteNodeLabel(flow_name="flow", node_name="")) is None + assert script.get_inherited_node(AbsoluteNodeLabel(flow_name="flow", node_name="node")) == Node( + misc={"k1": "g1", "k2": "l1", "k3": "n1", "k4": "n2", "k5": "n3"} + ) + # assert not changed + assert script.global_node == global_node + assert script.get_flow("flow").local_node == local_node + assert script.get_node(AbsoluteNodeLabel(flow_name="flow", node_name="node")) == node diff --git a/tests/core/test_script_function.py b/tests/core/test_script_function.py new file mode 100644 index 000000000..a5e51b643 --- /dev/null +++ b/tests/core/test_script_function.py @@ -0,0 +1,142 @@ +import pytest + +from chatsky.core.script_function import ConstResponse, ConstDestination, ConstCondition, ConstPriority +from chatsky.core.script_function import BasePriority, BaseCondition, BaseResponse, BaseDestination, BaseProcessing +from chatsky.core.script_function import logger +from chatsky.core import Message, Pipeline, Context, Node, Transition +from chatsky.core.node_label import AbsoluteNodeLabel, NodeLabel + + +class TestBaseFunctionCallWrapper: + @pytest.mark.parametrize( + "func_type,data,return_value", + [ + (BaseResponse, "text", Message(text="text")), + (BaseCondition, False, False), + (BaseDestination, ("flow", "node"), AbsoluteNodeLabel(flow_name="flow", node_name="node")), + (BaseProcessing, None, None), + (BasePriority, 1.0, 1.0), + ], + ) + async def test_validation(self, func_type, data, return_value): + class MyFunc(func_type): + async def call(self, ctx): + return data + + assert await MyFunc().wrapped_call(None) == return_value + + async def test_wrong_type(self): + class MyProc(BasePriority): + async def call(self, ctx): + return "w" + + assert isinstance(await MyProc().wrapped_call(None), TypeError) + + async def test_non_async_func(self): + class MyCondition(BaseCondition): + def call(self, ctx): + return True + + assert await MyCondition().wrapped_call(None) is True + + async def test_catch_exception(self, log_event_catcher): + log_list = log_event_catcher(logger) + + class MyProc(BaseProcessing): + async def call(self, ctx): + raise RuntimeError() + + assert isinstance(await MyProc().wrapped_call(None), RuntimeError) + assert len(log_list) == 1 + assert log_list[0].levelname == "WARNING" + + async def test_base_exception_not_handled(self): + class SpecialException(BaseException): + pass + + class MyProc(BaseProcessing): + async def call(self, ctx): + raise SpecialException() + + with pytest.raises(SpecialException): + await MyProc().wrapped_call(None) + + +@pytest.mark.parametrize( + "func_type,data,root_value,return_value", + [ + (ConstResponse, "response_text", Message(text="response_text"), Message(text="response_text")), + (ConstResponse, {"text": "response_text"}, Message(text="response_text"), Message(text="response_text")), + (ConstResponse, Message(text="response_text"), Message(text="response_text"), Message(text="response_text")), + ( + ConstDestination, + ("flow", "node"), + NodeLabel(flow_name="flow", node_name="node"), + AbsoluteNodeLabel(flow_name="flow", node_name="node"), + ), + ( + ConstDestination, + NodeLabel(flow_name="flow", node_name="node"), + NodeLabel(flow_name="flow", node_name="node"), + AbsoluteNodeLabel(flow_name="flow", node_name="node"), + ), + (ConstPriority, 1.0, 1.0, 1.0), + (ConstPriority, None, None, None), + (ConstCondition, False, False, False), + ], +) +async def test_const_functions(func_type, data, root_value, return_value): + func = func_type.model_validate(data) + assert func.root == root_value + + assert await func.wrapped_call(None) == return_value + + +class TestNodeLabelValidation: + @pytest.fixture + def pipeline(self): + return Pipeline(script={"flow1": {"node": {}}, "flow2": {"node": {}}}, start_label=("flow1", "node")) + + @pytest.fixture + def context_flow_factory(self, pipeline): + def factory(flow_name: str): + ctx = Context.init((flow_name, "node")) + ctx.framework_data.pipeline = pipeline + return ctx + + return factory + + @pytest.mark.parametrize("flow_name", ("flow1", "flow2")) + async def test_const_destination(self, context_flow_factory, flow_name): + const_dst = ConstDestination.model_validate("node") + + dst = await const_dst.wrapped_call(context_flow_factory(flow_name)) + assert dst.flow_name == flow_name + + @pytest.mark.parametrize("flow_name", ("flow1", "flow2")) + async def test_base_destination(self, context_flow_factory, flow_name): + class MyDestination(BaseDestination): + def call(self, ctx): + return "node" + + dst = await MyDestination().wrapped_call(context_flow_factory(flow_name)) + assert dst.flow_name == flow_name + + +def test_response_from_dict_validation(): + Node.model_validate({"response": {"msg": "text"}}) + + +def test_destination_from_dict_validation(): + Transition.model_validate({"dst": {"flow_name": "flow", "node_name": "node"}}) + + +async def test_const_object_immutability(): + message = Message(text="text1") + response = ConstResponse.model_validate(message) + + response_result = await response.wrapped_call(Context.init(("", ""))) + + response_result.text = "text2" + + assert message.text == "text1" diff --git a/tests/core/test_transition.py b/tests/core/test_transition.py new file mode 100644 index 000000000..350374c66 --- /dev/null +++ b/tests/core/test_transition.py @@ -0,0 +1,61 @@ +from typing import Union + +import pytest + +from chatsky.core import Transition as Tr, BaseDestination, BaseCondition, BasePriority, Context +from chatsky.core.transition import get_next_label, AbsoluteNodeLabel +from chatsky.core.node_label import NodeLabelInitTypes + + +class FaultyDestination(BaseDestination): + async def call(self, ctx: Context) -> NodeLabelInitTypes: + raise RuntimeError() + + +class FaultyCondition(BaseCondition): + async def call(self, ctx: Context) -> bool: + raise RuntimeError() + + +class FaultyPriority(BasePriority): + async def call(self, ctx: Context) -> Union[float, bool, None]: + raise RuntimeError() + + +class TruePriority(BasePriority): + async def call(self, ctx: Context) -> Union[float, bool, None]: + return True + + +class FalsePriority(BasePriority): + async def call(self, ctx: Context) -> Union[float, bool, None]: + return False + + +@pytest.mark.parametrize( + "transitions,default_priority,result", + [ + ([Tr(dst=("service", "start"))], 0, ("service", "start")), + ([Tr(dst="node1")], 0, ("flow", "node1")), + ([Tr(dst="node1"), Tr(dst="node2")], 0, ("flow", "node1")), + ([Tr(dst="node1"), Tr(dst="node2", priority=1)], 0, ("flow", "node2")), + ([Tr(dst="node1"), Tr(dst="node2", priority=1)], 2, ("flow", "node1")), + ([Tr(dst="node1", cnd=False), Tr(dst="node2")], 0, ("flow", "node2")), + ([Tr(dst="node1", cnd=False), Tr(dst="node2", cnd=False)], 0, None), + ([Tr(dst="non_existent")], 0, None), + ([Tr(dst=FaultyDestination())], 0, None), + ([Tr(dst="node1", priority=FaultyPriority())], 0, None), + ([Tr(dst="node1", cnd=FaultyCondition())], 0, None), + ([Tr(dst="node1", priority=FalsePriority())], 0, None), + ([Tr(dst="node1", priority=TruePriority()), Tr(dst="node2", priority=1)], 0, ("flow", "node2")), + ([Tr(dst="node1", priority=TruePriority()), Tr(dst="node2", priority=1)], 2, ("flow", "node1")), + ([Tr(dst="node1", priority=1), Tr(dst="node2", priority=2), Tr(dst="node3", priority=3)], 0, ("flow", "node3")), + ], +) +async def test_get_next_label(context_factory, transitions, default_priority, result): + ctx = context_factory() + ctx.add_label(("flow", "node1")) + + assert await get_next_label(ctx, transitions, default_priority) == ( + AbsoluteNodeLabel.model_validate(result) if result is not None else None + ) diff --git a/tests/messengers/telegram/test_tutorials.py b/tests/messengers/telegram/test_tutorials.py index 1893b3dd4..49da8dac9 100644 --- a/tests/messengers/telegram/test_tutorials.py +++ b/tests/messengers/telegram/test_tutorials.py @@ -5,7 +5,7 @@ import pytest from chatsky.messengers.telegram import telegram_available -from chatsky.script.core.message import DataAttachment +from chatsky.core.message import DataAttachment from tests.test_utils import get_path_from_tests_to_current_dir if telegram_available: diff --git a/tests/messengers/telegram/utils.py b/tests/messengers/telegram/utils.py index d54816bd4..71f83bc79 100644 --- a/tests/messengers/telegram/utils.py +++ b/tests/messengers/telegram/utils.py @@ -9,8 +9,7 @@ from typing_extensions import TypeAlias from chatsky.messengers.telegram.abstract import _AbstractTelegramInterface -from chatsky.script import Message -from chatsky.script.core.context import Context +from chatsky.core import Message, Context PathStep: TypeAlias = Tuple[Update, Message, Message, List[str]] diff --git a/tests/pipeline/test_messenger_interface.py b/tests/pipeline/test_messenger_interface.py index 545d41c2f..4f41890e8 100644 --- a/tests/pipeline/test_messenger_interface.py +++ b/tests/pipeline/test_messenger_interface.py @@ -2,31 +2,23 @@ import sys import pathlib -from chatsky.script import RESPONSE, TRANSITIONS, Message +from chatsky.core import RESPONSE, TRANSITIONS, Message, Pipeline, Transition as Tr from chatsky.messengers.console import CLIMessengerInterface from chatsky.messengers.common import CallbackMessengerInterface -from chatsky.pipeline import Pipeline -import chatsky.script.conditions as cnd +import chatsky.conditions as cnd SCRIPT = { "pingpong_flow": { "start_node": { - RESPONSE: { - "text": "", - }, - TRANSITIONS: {"node1": cnd.exact_match("Ping")}, + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Ping"))], }, "node1": { - RESPONSE: { - "text": "Pong", - }, - TRANSITIONS: {"node1": cnd.exact_match("Ping")}, + RESPONSE: "Pong", + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Ping"))], }, "fallback_node": { - RESPONSE: { - "text": "Ooops", - }, - TRANSITIONS: {"node1": cnd.exact_match("Ping")}, + RESPONSE: "Ooops", + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Ping"))], }, } } @@ -61,4 +53,4 @@ def test_callback_messenger_interface(monkeypatch): pipeline.run() for _ in range(0, 5): - assert interface.on_request(Message("Ping"), 0).last_response == Message("Pong") + assert interface.on_request(Message(text="Ping"), 0).last_response == Message(text="Pong") diff --git a/tests/pipeline/test_parallel_processing.py b/tests/pipeline/test_parallel_processing.py deleted file mode 100644 index 4c23e344a..000000000 --- a/tests/pipeline/test_parallel_processing.py +++ /dev/null @@ -1,43 +0,0 @@ -import asyncio - -import pytest - -from chatsky.script import Message, GLOBAL, RESPONSE, PRE_RESPONSE_PROCESSING, TRANSITIONS, conditions as cnd -from chatsky.pipeline import Pipeline - - -@pytest.mark.asyncio -async def test_parallel_processing(): - async def fast_processing(ctx, _): - processed_node = ctx.current_node - await asyncio.sleep(1) - processed_node.response = Message(f"fast: {processed_node.response.text}") - - async def slow_processing(ctx, _): - processed_node = ctx.current_node - await asyncio.sleep(2) - processed_node.response = Message(f"slow: {processed_node.response.text}") - - toy_script = { - GLOBAL: { - PRE_RESPONSE_PROCESSING: { - "first": slow_processing, - "second": fast_processing, - } - }, - "root": {"start": {TRANSITIONS: {"main": cnd.true()}}, "main": {RESPONSE: Message("text")}}, - } - - # test sequential processing - pipeline = Pipeline(script=toy_script, start_label=("root", "start"), parallelize_processing=False) - - ctx = await pipeline._run_pipeline(Message(), 0) - - assert ctx.last_response.text == "fast: slow: text" - - # test parallel processing - pipeline = Pipeline(script=toy_script, start_label=("root", "start"), parallelize_processing=True) - - ctx = await pipeline._run_pipeline(Message(), 0) - - assert ctx.last_response.text == "slow: fast: text" diff --git a/tests/pipeline/test_pipeline.py b/tests/pipeline/test_pipeline.py deleted file mode 100644 index 8d385e2c8..000000000 --- a/tests/pipeline/test_pipeline.py +++ /dev/null @@ -1,16 +0,0 @@ -from chatsky.script import Message -from chatsky.pipeline import Pipeline -from chatsky.script.core.keywords import RESPONSE, TRANSITIONS -import chatsky.script.conditions as cnd - - -def test_script_getting_and_setting(): - script = {"old_flow": {"": {RESPONSE: lambda _, __: Message(), TRANSITIONS: {"": cnd.true()}}}} - pipeline = Pipeline(script=script, start_label=("old_flow", "")) - - new_script = {"new_flow": {"": {RESPONSE: lambda _, __: Message(), TRANSITIONS: {"": cnd.false()}}}} - # IDE gives the warning that "Property 'script' cannot be set" - # And yet the test still passes. - pipeline.script = new_script - pipeline.start_label = ("new_flow", "") - assert list(pipeline.script.keys())[0] == list(new_script.keys())[0] diff --git a/tests/pipeline/test_update_ctx_misc.py b/tests/pipeline/test_update_ctx_misc.py index 4fc2fddb8..fb5251c1d 100644 --- a/tests/pipeline/test_update_ctx_misc.py +++ b/tests/pipeline/test_update_ctx_misc.py @@ -1,20 +1,21 @@ import pytest -from chatsky.pipeline import Pipeline -from chatsky.script import Message, RESPONSE, TRANSITIONS +from chatsky import Context +from chatsky.core import Message, RESPONSE, TRANSITIONS, Pipeline, Transition as Tr, BaseCondition @pytest.mark.asyncio async def test_update_ctx_misc(): - def condition(ctx, _): - return ctx.misc["condition"] + class MyCondition(BaseCondition): + async def call(self, ctx: Context) -> bool: + return ctx.misc["condition"] toy_script = { "root": { - "start": {TRANSITIONS: {"success": condition}}, - "success": {RESPONSE: Message("success"), TRANSITIONS: {"success": condition}}, + "start": {TRANSITIONS: [Tr(dst="success", cnd=MyCondition())]}, + "success": {RESPONSE: "success", TRANSITIONS: [Tr(dst="success", cnd=MyCondition())]}, "failure": { - RESPONSE: Message("failure"), + RESPONSE: "failure", }, } } diff --git a/tests/pipeline/test_validation.py b/tests/pipeline/test_validation.py index c245daed6..903d24d3f 100644 --- a/tests/pipeline/test_validation.py +++ b/tests/pipeline/test_validation.py @@ -1,19 +1,17 @@ from typing import Callable -from chatsky.pipeline.pipeline.component import PipelineComponent from pydantic import ValidationError import pytest -from chatsky.pipeline import ( - Pipeline, +from chatsky.core.service import ( Service, ServiceGroup, - Actor, ServiceRuntimeInfo, BeforeHandler, + PipelineComponent, ) -from chatsky.script import Context -from chatsky.utils.testing import TOY_SCRIPT_KWARGS +from chatsky.core import Context, Pipeline +from chatsky.utils.testing import TOY_SCRIPT_KWARGS, TOY_SCRIPT # Looks overly long, we only need one function anyway. @@ -116,7 +114,7 @@ def test_wrong_inputs(self): # Though 123 will be cast to a list ServiceGroup(components=123) with pytest.raises(ValidationError): - # The dictionary inside 'components' will check if Actor, Service or ServiceGroup fit the signature, + # The dictionary inside 'components' will check if Service or ServiceGroup fit the signature, # but it doesn't fit any of them (required fields are not defined), so it's just a normal dictionary. ServiceGroup(components={"before_handler": []}) with pytest.raises(ValidationError): @@ -125,35 +123,32 @@ def test_wrong_inputs(self): ServiceGroup(components={"handler": 123}) -# Testing of node and script validation for actor exist at script/core/test_actor.py -class TestActorValidation: - def test_toy_script_actor(self): - Actor(**TOY_SCRIPT_KWARGS) - - def test_wrong_inputs(self): - with pytest.raises(ValidationError): - # 'condition_handler' is not an Optional field. - Actor(**TOY_SCRIPT_KWARGS, condition_handler=None) - with pytest.raises(ValidationError): - # 'handlers' is not an Optional field. - Actor(**TOY_SCRIPT_KWARGS, handlers=None) - with pytest.raises(ValidationError): - # 'script' must be either a dict or Script instance. - Actor(script=[], start_label=TOY_SCRIPT_KWARGS["start_label"]) - - # Can't think of any other tests that aren't done in other tests in this file class TestPipelineValidation: def test_correct_inputs(self): Pipeline(**TOY_SCRIPT_KWARGS) Pipeline.model_validate(TOY_SCRIPT_KWARGS) - # Testing if actor is an unchangeable constant throughout the program - def test_cached_property(self): + def test_fallback_label_set_to_start_label(self): + pipeline = Pipeline(script=TOY_SCRIPT, start_label=("greeting_flow", "start_node")) + assert pipeline.fallback_label.node_name == "start_node" + + def test_incorrect_labels(self): + with pytest.raises(ValidationError): + Pipeline(script=TOY_SCRIPT, start_label=("nonexistent", "nonexistent")) + + with pytest.raises(ValidationError): + Pipeline( + script=TOY_SCRIPT, + start_label=("greeting_flow", "start_node"), + fallback_label=("nonexistent", "nonexistent"), + ) + + def test_pipeline_services_cached(self): pipeline = Pipeline(**TOY_SCRIPT_KWARGS) - old_actor_id = id(pipeline.actor) + old_actor_id = id(pipeline.services_pipeline) pipeline.fallback_label = ("greeting_flow", "other_node") - assert old_actor_id == id(pipeline.actor) + assert old_actor_id == id(pipeline.services_pipeline) def test_pre_services(self): with pytest.raises(ValidationError): diff --git a/tests/script/conditions/__init__.py b/tests/script/conditions/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/script/conditions/test_conditions.py b/tests/script/conditions/test_conditions.py deleted file mode 100644 index f8ae26103..000000000 --- a/tests/script/conditions/test_conditions.py +++ /dev/null @@ -1,64 +0,0 @@ -# %% -from chatsky.pipeline import Pipeline -from chatsky.script import Context, Message -import chatsky.script.conditions as cnd - - -def test_conditions(): - label = ("flow", "node") - ctx = Context() - ctx.add_request(Message("text", misc={})) - ctx.add_label(label) - failed_ctx = Context() - failed_ctx.add_request(Message()) - failed_ctx.add_label(label) - pipeline = Pipeline(script={"flow": {"node": {}}}, start_label=("flow", "node")) - - assert cnd.exact_match("text")(ctx, pipeline) - assert cnd.exact_match(Message("text", misc={}))(ctx, pipeline) - assert not cnd.exact_match(Message("text", misc={"one": 1}))(ctx, pipeline) - assert not cnd.exact_match("text1")(ctx, pipeline) - assert cnd.exact_match(Message())(ctx, pipeline) - assert not cnd.exact_match(Message(), skip_none=False)(ctx, pipeline) - assert cnd.exact_match("text")(ctx, pipeline) - assert not cnd.exact_match("text1")(ctx, pipeline) - - assert cnd.has_text("text")(ctx, pipeline) - assert cnd.has_text("te")(ctx, pipeline) - assert not cnd.has_text("text1")(ctx, pipeline) - assert cnd.has_text("")(ctx, pipeline) - - assert cnd.regexp("t.*t")(ctx, pipeline) - assert not cnd.regexp("t.*t1")(ctx, pipeline) - assert not cnd.regexp("t.*t1")(failed_ctx, pipeline) - - assert cnd.agg([cnd.regexp("t.*t"), cnd.exact_match("text")], aggregate_func=all)(ctx, pipeline) - assert not cnd.agg([cnd.regexp("t.*t1"), cnd.exact_match("text")], aggregate_func=all)(ctx, pipeline) - - assert cnd.any([cnd.regexp("t.*t1"), cnd.exact_match("text")])(ctx, pipeline) - assert not cnd.any([cnd.regexp("t.*t1"), cnd.exact_match("text1")])(ctx, pipeline) - - assert cnd.all([cnd.regexp("t.*t"), cnd.exact_match("text")])(ctx, pipeline) - assert not cnd.all([cnd.regexp("t.*t1"), cnd.exact_match("text")])(ctx, pipeline) - - assert cnd.neg(cnd.exact_match("text1"))(ctx, pipeline) - assert not cnd.neg(cnd.exact_match("text"))(ctx, pipeline) - - assert cnd.has_last_labels(flow_labels=["flow"])(ctx, pipeline) - assert not cnd.has_last_labels(flow_labels=["flow1"])(ctx, pipeline) - - assert cnd.has_last_labels(labels=[("flow", "node")])(ctx, pipeline) - assert not cnd.has_last_labels(labels=[("flow", "node1")])(ctx, pipeline) - - assert cnd.true()(ctx, pipeline) - assert not cnd.false()(ctx, pipeline) - - try: - cnd.any([123]) - except TypeError: - pass - - def failed_cond_func(ctx: Context, pipeline: Pipeline) -> bool: - raise ValueError("Failed cnd") - - assert not cnd.any([failed_cond_func])(ctx, pipeline) diff --git a/tests/script/core/__init__.py b/tests/script/core/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/script/core/test_actor.py b/tests/script/core/test_actor.py deleted file mode 100644 index b840fdc3e..000000000 --- a/tests/script/core/test_actor.py +++ /dev/null @@ -1,200 +0,0 @@ -# %% -import pytest -from chatsky.pipeline import Pipeline, ComponentExecutionState -from chatsky.script import ( - TRANSITIONS, - RESPONSE, - GLOBAL, - LOCAL, - PRE_TRANSITIONS_PROCESSING, - PRE_RESPONSE_PROCESSING, - Context, - Message, -) -from chatsky.script.conditions import true -from chatsky.script.labels import repeat - - -def positive_test(samples, custom_class): - results = [] - for sample in samples: - try: - res = custom_class(**sample) - results += [res] - except Exception as exception: - raise Exception(f"sample={sample} gets exception={exception}") - return results - - -def negative_test(samples, custom_class): - for sample in samples: - try: - custom_class(**sample) - except Exception: # TODO: special type of exceptions - continue - raise Exception(f"sample={sample} can not be passed") - - -def std_func(ctx, pipeline): - pass - - -def fake_label(ctx: Context, pipeline): - return ("flow", "node1", 1) - - -def raised_response(ctx: Context, pipeline): - raise Exception("") - - -@pytest.mark.asyncio -async def test_actor(): - try: - # fail of start label - Pipeline(script={"flow": {"node1": {}}}, start_label=("flow1", "node1")) - raise Exception("can not be passed: fail of start label") - except ValueError: - pass - try: - # fail of fallback label - Pipeline(script={"flow": {"node1": {}}}, start_label=("flow", "node1"), fallback_label=("flow1", "node1")) - raise Exception("can not be passed: fail of fallback label") - except ValueError: - pass - try: - # fail of missing node - Pipeline(script={"flow": {"node1": {TRANSITIONS: {"miss_node1": true()}}}}, start_label=("flow", "node1")) - raise Exception("can not be passed: fail of missing node") - except ValueError: - pass - try: - # fail of response returned Callable - pipeline = Pipeline( - script={"flow": {"node1": {RESPONSE: lambda c, a: lambda x: 1, TRANSITIONS: {repeat(): true()}}}}, - start_label=("flow", "node1"), - ) - ctx = Context() - await pipeline.actor(ctx, pipeline) - assert pipeline.actor.get_state(ctx) is not ComponentExecutionState.FAILED - raise Exception("can not be passed: fail of response returned Callable") - except AssertionError: - pass - - # empty ctx stability - pipeline = Pipeline(script={"flow": {"node1": {TRANSITIONS: {"node1": true()}}}}, start_label=("flow", "node1")) - ctx = Context() - await pipeline.actor(ctx, pipeline) - - # fake label stability - pipeline = Pipeline(script={"flow": {"node1": {TRANSITIONS: {fake_label: true()}}}}, start_label=("flow", "node1")) - ctx = Context() - await pipeline.actor(ctx, pipeline) - - -limit_errors = {} - - -def check_call_limit(limit: int = 1, default_value=None, label=""): - counter = 0 - - def call_limit_handler(ctx: Context, pipeline): - nonlocal counter - counter += 1 - if counter > limit: - msg = f"calls are out of limits counterlimit={counter}/{limit} for {default_value} and {label}" - limit_errors[call_limit_handler] = msg - if default_value == "ctx": - return ctx - return default_value - - return call_limit_handler - - -@pytest.mark.asyncio -async def test_call_limit(): - script = { - GLOBAL: { - TRANSITIONS: { - check_call_limit(4, ("flow1", "node1", 0.0), "global label"): check_call_limit(4, True, "global cond") - }, - PRE_TRANSITIONS_PROCESSING: {"tpg": check_call_limit(4, "ctx", "tpg")}, - PRE_RESPONSE_PROCESSING: {"rpg": check_call_limit(4, "ctx", "rpg")}, - }, - "flow1": { - LOCAL: { - TRANSITIONS: { - check_call_limit(2, ("flow1", "node1", 0.0), "local label for flow1"): check_call_limit( - 2, True, "local cond for flow1" - ) - }, - PRE_TRANSITIONS_PROCESSING: {"tpl": check_call_limit(2, "ctx", "tpl")}, - PRE_RESPONSE_PROCESSING: {"rpl": check_call_limit(3, "ctx", "rpl")}, - }, - "node1": { - RESPONSE: check_call_limit(1, Message("r1"), "flow1_node1"), - PRE_TRANSITIONS_PROCESSING: {"tp1": check_call_limit(1, "ctx", "flow1_node1_tp1")}, - TRANSITIONS: { - check_call_limit(1, ("flow1", "node2"), "cond flow1_node2"): check_call_limit( - 1, - True, - "cond flow1_node2", - ) - }, - PRE_RESPONSE_PROCESSING: {"rp1": check_call_limit(1, "ctx", "flow1_node1_rp1")}, - }, - "node2": { - RESPONSE: check_call_limit(1, Message("r1"), "flow1_node2"), - PRE_TRANSITIONS_PROCESSING: {"tp1": check_call_limit(1, "ctx", "flow1_node2_tp1")}, - TRANSITIONS: { - check_call_limit(1, ("flow2", "node1"), "cond flow2_node1"): check_call_limit( - 1, - True, - "cond flow2_node1", - ) - }, - PRE_RESPONSE_PROCESSING: {"rp1": check_call_limit(1, "ctx", "flow1_node2_rp1")}, - }, - }, - "flow2": { - LOCAL: { - TRANSITIONS: { - check_call_limit(2, ("flow1", "node1", 0.0), "local label for flow2"): check_call_limit( - 2, True, "local cond for flow2" - ) - }, - PRE_TRANSITIONS_PROCESSING: {"tpl": check_call_limit(2, "ctx", "tpl")}, - PRE_RESPONSE_PROCESSING: {"rpl": check_call_limit(2, "ctx", "rpl")}, - }, - "node1": { - RESPONSE: check_call_limit(1, Message("r1"), "flow2_node1"), - PRE_TRANSITIONS_PROCESSING: {"tp1": check_call_limit(1, "ctx", "flow2_node1_tp1")}, - TRANSITIONS: { - check_call_limit(1, ("flow2", "node2"), "label flow2_node2"): check_call_limit( - 1, - True, - "cond flow2_node2", - ) - }, - PRE_RESPONSE_PROCESSING: {"rp1": check_call_limit(1, "ctx", "flow2_node1_rp1")}, - }, - "node2": { - RESPONSE: check_call_limit(1, Message("r1"), "flow2_node2"), - PRE_TRANSITIONS_PROCESSING: {"tp1": check_call_limit(1, "ctx", "flow2_node2_tp1")}, - TRANSITIONS: { - check_call_limit(1, ("flow1", "node1"), "label flow2_node2"): check_call_limit( - 1, - True, - "cond flow2_node2", - ) - }, - PRE_RESPONSE_PROCESSING: {"rp1": check_call_limit(1, "ctx", "flow2_node2_rp1")}, - }, - }, - } - # script = {"flow": {"node1": {TRANSITIONS: {"node1": true()}}}} - pipeline = Pipeline(script=script, start_label=("flow1", "node1")) - for i in range(4): - await pipeline._run_pipeline(Message("req1"), 0) - if limit_errors: - error_msg = repr(limit_errors) - raise Exception(error_msg) diff --git a/tests/script/core/test_context.py b/tests/script/core/test_context.py deleted file mode 100644 index a218ac15b..000000000 --- a/tests/script/core/test_context.py +++ /dev/null @@ -1,59 +0,0 @@ -# %% -import random - -from chatsky.script import Context, Message - - -def shuffle_dict_keys(dictionary: dict) -> dict: - return {key: dictionary[key] for key in sorted(dictionary, key=lambda k: random.random())} - - -def test_context(): - ctx = Context() - for index in range(0, 30, 2): - ctx.add_request(Message(str(index))) - ctx.add_label((str(index), str(index + 1))) - ctx.add_response(Message(str(index + 1))) - ctx.labels = shuffle_dict_keys(ctx.labels) - ctx.requests = shuffle_dict_keys(ctx.requests) - ctx.responses = shuffle_dict_keys(ctx.responses) - ctx = Context.cast(ctx.model_dump_json()) - ctx.misc[123] = 312 - ctx.clear(5, ["requests", "responses", "misc", "labels", "framework_data"]) - ctx.misc["1001"] = "11111" - ctx.add_request(Message(str(1000))) - ctx.add_label((str(1000), str(1000 + 1))) - ctx.add_response(Message(str(1000 + 1))) - - assert ctx.labels == { - 10: ("20", "21"), - 11: ("22", "23"), - 12: ("24", "25"), - 13: ("26", "27"), - 14: ("28", "29"), - 15: ("1000", "1001"), - } - assert ctx.requests == { - 10: Message("20"), - 11: Message("22"), - 12: Message("24"), - 13: Message("26"), - 14: Message("28"), - 15: Message("1000"), - } - assert ctx.responses == { - 10: Message("21"), - 11: Message("23"), - 12: Message("25"), - 13: Message("27"), - 14: Message("29"), - 15: Message("1001"), - } - assert ctx.misc == {"1001": "11111"} - assert ctx.current_node is None - ctx.model_dump_json() - - try: - Context.cast(123) - except ValueError: - pass diff --git a/tests/script/core/test_normalization.py b/tests/script/core/test_normalization.py deleted file mode 100644 index 347486949..000000000 --- a/tests/script/core/test_normalization.py +++ /dev/null @@ -1,128 +0,0 @@ -# %% -from typing import Tuple - -from chatsky.pipeline import Pipeline -from chatsky.script import ( - GLOBAL, - TRANSITIONS, - RESPONSE, - MISC, - PRE_RESPONSE_PROCESSING, - PRE_TRANSITIONS_PROCESSING, - Context, - Script, - Node, - ConstLabel, - Message, -) -from chatsky.script.labels import repeat -from chatsky.script.conditions import true - -from chatsky.script.core.normalization import normalize_condition, normalize_label, normalize_response - - -def std_func(ctx, pipeline): - pass - - -def create_env() -> Tuple[Context, Pipeline]: - ctx = Context() - script = {"flow": {"node1": {TRANSITIONS: {repeat(): true()}, RESPONSE: Message("response")}}} - pipeline = Pipeline(script=script, start_label=("flow", "node1"), fallback_label=("flow", "node1")) - ctx.add_request(Message("text")) - return ctx, pipeline - - -def test_normalize_label(): - ctx, actor = create_env() - - def true_label_func(ctx: Context, pipeline: Pipeline) -> ConstLabel: - return ("flow", "node1", 1) - - def false_label_func(ctx: Context, pipeline: Pipeline) -> ConstLabel: - return ("flow", "node2", 1) - - n_f = normalize_label(true_label_func) - assert callable(n_f) - assert n_f(ctx, actor) == ("flow", "node1", 1) - n_f = normalize_label(false_label_func) - assert n_f(ctx, actor) is None - - assert normalize_label("node", "flow") == ("flow", "node", float("-inf")) - assert normalize_label(("flow", "node"), "flow") == ("flow", "node", float("-inf")) - assert normalize_label(("flow", "node", 1.0), "flow") == ("flow", "node", 1.0) - assert normalize_label(("node", 1.0), "flow") == ("flow", "node", 1.0) - - -def test_normalize_condition(): - ctx, actor = create_env() - - def true_condition_func(ctx: Context, pipeline: Pipeline) -> bool: - return True - - def false_condition_func(ctx: Context, pipeline: Pipeline) -> bool: - raise Exception("False condition") - - n_f = normalize_condition(true_condition_func) - assert callable(n_f) - flag = n_f(ctx, actor) - assert isinstance(flag, bool) and flag - n_f = normalize_condition(false_condition_func) - flag = n_f(ctx, actor) - assert isinstance(flag, bool) and not flag - - assert callable(normalize_condition(std_func)) - - -def test_normalize_transitions(): - trans = Node.normalize_transitions({("flow", "node", 1.0): std_func}) - assert list(trans)[0] == ("flow", "node", 1.0) - assert callable(list(trans.values())[0]) - - -def test_normalize_response(): - assert callable(normalize_response(std_func)) - assert callable(normalize_response(Message("text"))) - - -def test_normalize_keywords(): - node_template = { - TRANSITIONS: {"node": std_func}, - RESPONSE: Message("text"), - PRE_RESPONSE_PROCESSING: {1: std_func}, - PRE_TRANSITIONS_PROCESSING: {1: std_func}, - MISC: {"key": "val"}, - } - node_template_gold = { - TRANSITIONS.name.lower(): {"node": std_func}, - RESPONSE.name.lower(): Message("text"), - PRE_RESPONSE_PROCESSING.name.lower(): {1: std_func}, - PRE_TRANSITIONS_PROCESSING.name.lower(): {1: std_func}, - MISC.name.lower(): {"key": "val"}, - } - script = {"flow": {"node": node_template.copy()}} - assert isinstance(script, dict) - assert script["flow"]["node"] == node_template_gold - - -def test_normalize_script(): - # TODO: Add full check for functions - node_template = { - TRANSITIONS: {"node": std_func}, - RESPONSE: Message("text"), - PRE_RESPONSE_PROCESSING: {1: std_func}, - PRE_TRANSITIONS_PROCESSING: {1: std_func}, - MISC: {"key": "val"}, - } - node_template_gold = { - TRANSITIONS.name.lower(): {"node": std_func}, - RESPONSE.name.lower(): Message("text"), - PRE_RESPONSE_PROCESSING.name.lower(): {1: std_func}, - PRE_TRANSITIONS_PROCESSING.name.lower(): {1: std_func}, - MISC.name.lower(): {"key": "val"}, - } - script = {GLOBAL: node_template.copy(), "flow": {"node": node_template.copy()}} - script = Script.normalize_script(script) - assert isinstance(script, dict) - assert script[GLOBAL][GLOBAL] == node_template_gold - assert script["flow"]["node"] == node_template_gold diff --git a/tests/script/core/test_script.py b/tests/script/core/test_script.py deleted file mode 100644 index 8665b1d27..000000000 --- a/tests/script/core/test_script.py +++ /dev/null @@ -1,122 +0,0 @@ -# %% -import itertools - -import pytest - -from chatsky.script import ( - TRANSITIONS, - RESPONSE, - MISC, - PRE_RESPONSE_PROCESSING, - PRE_TRANSITIONS_PROCESSING, - Script, - Node, - Message, -) -from chatsky.utils.testing.toy_script import TOY_SCRIPT, MULTIFLOW_SCRIPT - - -def positive_test(samples, custom_class): - results = [] - for sample in samples: - try: - res = custom_class(**sample) - results += [res] - except Exception as exception: - raise Exception(f"sample={sample} gets exception={exception}") - return results - - -def negative_test(samples, custom_class): - for sample in samples: - try: - custom_class(**sample) - except Exception: # TODO: special type of exceptions - continue - raise Exception(f"sample={sample} can not be passed") - - -def std_func(ctx, pipeline): - pass - - -def test_node_creation(): - node_creation(PRE_RESPONSE_PROCESSING) - - -def node_creation(pre_response_proc): - samples = { - "transition": [std_func, "node", ("flow", "node"), ("node", 2.0), ("flow", "node", 2.0)], - "condition": [std_func], - RESPONSE.name.lower(): [Message("text"), std_func, None], - pre_response_proc.name.lower(): [{}, {1: std_func}, None], - PRE_TRANSITIONS_PROCESSING.name.lower(): [{}, {1: std_func}, None], - MISC.name.lower(): [{}, {1: "var"}, None], - } - samples = [ - { - TRANSITIONS.name.lower(): {transition: condition}, - RESPONSE.name.lower(): response, - pre_response_proc.name.lower(): pre_response, - PRE_TRANSITIONS_PROCESSING.name.lower(): pre_transitions, - MISC.name.lower(): misc, - } - for transition, condition, response, pre_response, pre_transitions, misc in itertools.product( - *list(samples.values()) - ) - ] - samples = [{k: v for k, v in sample.items() if v is not None} for sample in samples] - positive_test(samples, Node) - - samples = { - "transition": [None], - "condition": [None, 123, "asdasd", 2.0, [], {}], - pre_response_proc.name.lower(): [123, "asdasd", 2.0, {1: None}, {1: 123}, {1: 2.0}, {1: []}, {1: {}}], - PRE_TRANSITIONS_PROCESSING.name.lower(): [123, "asdasd", 2.0, {1: None}, {1: 123}, {1: 2.0}, {1: []}, {1: {}}], - MISC.name.lower(): [123, "asdasd", 2.0], - } - samples = [ - { - TRANSITIONS.name.lower(): {val if key == "transition" else "node": val if key == "condition" else std_func}, - RESPONSE.name.lower(): val if key == RESPONSE.name.lower() else None, - pre_response_proc.name.lower(): val if key == pre_response_proc.name.lower() else None, - PRE_TRANSITIONS_PROCESSING.name.lower(): val if key == PRE_TRANSITIONS_PROCESSING.name.lower() else None, - MISC.name.lower(): val if key == MISC.name.lower() else None, - } - for key, values in samples.items() - for val in values - ] - samples = [{k: v for k, v in sample.items() if v is not None} for sample in samples] - negative_test(samples, Node) - - -def node_test(node: Node): - assert list(node.transitions)[0] == ("", "node", float("-inf")) - assert callable(list(node.transitions.values())[0]) - assert isinstance(node.pre_response_processing, dict) - assert isinstance(node.pre_transitions_processing, dict) - assert node.misc == {"key": "val"} - - -def test_node_exec(): - node = Node( - **{ - TRANSITIONS.name.lower(): {"node": std_func}, - RESPONSE.name.lower(): Message("text"), - PRE_RESPONSE_PROCESSING.name.lower(): {1: std_func}, - PRE_TRANSITIONS_PROCESSING.name.lower(): {1: std_func}, - MISC.name.lower(): {"key": "val"}, - } - ) - node_test(node) - - -@pytest.mark.parametrize( - ["script"], - [ - (TOY_SCRIPT,), - (MULTIFLOW_SCRIPT,), - ], -) -def test_script(script): - Script(script=script) diff --git a/tests/script/core/test_validation.py b/tests/script/core/test_validation.py deleted file mode 100644 index 9068f335a..000000000 --- a/tests/script/core/test_validation.py +++ /dev/null @@ -1,215 +0,0 @@ -from pydantic import ValidationError -import pytest - -from chatsky.pipeline import Pipeline -from chatsky.script import ( - PRE_RESPONSE_PROCESSING, - PRE_TRANSITIONS_PROCESSING, - RESPONSE, - TRANSITIONS, - Context, - Message, - Script, - ConstLabel, -) -from chatsky.script.conditions import exact_match - - -class UserFunctionSamples: - """ - This class contains various examples of user functions along with their signatures. - """ - - @staticmethod - def wrong_param_number(number: int) -> float: - return 8.0 + number - - @staticmethod - def wrong_param_types(number: int, flag: bool) -> float: - return 8.0 + number if flag else 42.1 - - @staticmethod - def wrong_return_type(_: Context, __: Pipeline) -> float: - return 1.0 - - @staticmethod - def correct_label(_: Context, __: Pipeline) -> ConstLabel: - return ("root", "start", 1) - - @staticmethod - def correct_response(_: Context, __: Pipeline) -> Message: - return Message("hi") - - @staticmethod - def correct_condition(_: Context, __: Pipeline) -> bool: - return True - - @staticmethod - def correct_pre_response_processor(_: Context, __: Pipeline) -> None: - pass - - @staticmethod - def correct_pre_transition_processor(_: Context, __: Pipeline) -> None: - pass - - -class TestLabelValidation: - def test_param_number(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: - Script( - script={ - "root": { - "start": {TRANSITIONS: {UserFunctionSamples.wrong_param_number: exact_match(Message("hi"))}} - } - } - ) - assert e - - def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: - Script( - script={ - "root": { - "start": {TRANSITIONS: {UserFunctionSamples.wrong_param_types: exact_match(Message("hi"))}} - } - } - ) - assert e - - def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: - Script( - script={ - "root": { - "start": {TRANSITIONS: {UserFunctionSamples.wrong_return_type: exact_match(Message("hi"))}} - } - } - ) - assert e - - def test_flow_name(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Flow '\w*' cannot be found for label") as e: - Script(script={"root": {"start": {TRANSITIONS: {("other", "start", 1): exact_match(Message("hi"))}}}}) - assert e - - def test_node_name(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Node '\w*' cannot be found for label") as e: - Script(script={"root": {"start": {TRANSITIONS: {("root", "other", 1): exact_match(Message("hi"))}}}}) - assert e - - def test_correct_script(self): - Script( - script={"root": {"start": {TRANSITIONS: {UserFunctionSamples.correct_label: exact_match(Message("hi"))}}}} - ) - - -class TestResponseValidation: - def test_param_number(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: - Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_number}}}) - assert e - - def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: - Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_param_types}}}) - assert e - - def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: - Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.wrong_return_type}}}) - assert e - - def test_correct_script(self): - Script(script={"root": {"start": {RESPONSE: UserFunctionSamples.correct_response}}}) - - -class TestConditionValidation: - def test_param_number(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: - Script( - script={ - "root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_number}}} - } - ) - assert e - - def test_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: - Script( - script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_param_types}}}} - ) - assert e - - def test_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: - Script( - script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.wrong_return_type}}}} - ) - assert e - - def test_correct_script(self): - Script(script={"root": {"start": {TRANSITIONS: {("root", "start", 1): UserFunctionSamples.correct_condition}}}}) - - -class TestProcessingValidation: - def test_response_param_number(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: - Script( - script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_number}}}} - ) - assert e - - def test_response_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: - Script( - script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_param_types}}}} - ) - assert e - - def test_response_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: - Script( - script={"root": {"start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.wrong_return_type}}}} - ) - assert e - - def test_response_correct_script(self): - Script( - script={ - "root": { - "start": {PRE_RESPONSE_PROCESSING: {"PRP": UserFunctionSamples.correct_pre_response_processor}} - } - } - ) - - def test_transition_param_number(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter number") as e: - Script( - script={ - "root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_number}}} - } - ) - assert e - - def test_transition_param_types(self): - with pytest.raises(ValidationError, match=r"Found 3 errors:[\w\W]*Incorrect parameter annotation") as e: - Script( - script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_param_types}}}} - ) - assert e - - def test_transition_return_type(self): - with pytest.raises(ValidationError, match=r"Found 1 error:[\w\W]*Incorrect return type annotation") as e: - Script( - script={"root": {"start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.wrong_return_type}}}} - ) - assert e - - def test_transition_correct_script(self): - Script( - script={ - "root": { - "start": {PRE_TRANSITIONS_PROCESSING: {"PTP": UserFunctionSamples.correct_pre_transition_processor}} - } - } - ) diff --git a/tests/script/labels/__init__.py b/tests/script/labels/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/script/labels/test_labels.py b/tests/script/labels/test_labels.py deleted file mode 100644 index fcb109adb..000000000 --- a/tests/script/labels/test_labels.py +++ /dev/null @@ -1,44 +0,0 @@ -from chatsky.pipeline import Pipeline -from chatsky.script import Context -from chatsky.script.labels import forward, repeat, previous, to_fallback, to_start, backward - - -def test_labels(): - ctx = Context() - - pipeline = Pipeline( - script={"flow": {"node1": {}, "node2": {}, "node3": {}}, "service": {"start": {}, "fallback": {}}}, - start_label=("service", "start"), - fallback_label=("service", "fallback"), - ) - - assert repeat(99)(ctx, pipeline) == ("service", "start", 99) - assert previous(99)(ctx, pipeline) == ("service", "fallback", 99) - - ctx.add_label(["flow", "node1"]) - ctx.add_label(["flow", "node2"]) - ctx.add_label(["flow", "node3"]) - ctx.add_label(["flow", "node2"]) - - assert repeat(99)(ctx, pipeline) == ("flow", "node2", 99) - assert previous(99)(ctx, pipeline) == ("flow", "node3", 99) - assert to_fallback(99)(ctx, pipeline) == ("service", "fallback", 99) - assert to_start(99)(ctx, pipeline) == ("service", "start", 99) - assert forward(99)(ctx, pipeline) == ("flow", "node3", 99) - assert backward(99)(ctx, pipeline) == ("flow", "node1", 99) - - ctx.add_label(["flow", "node3"]) - assert forward(99)(ctx, pipeline) == ("flow", "node1", 99) - assert forward(99, cyclicality_flag=False)(ctx, pipeline) == ("service", "fallback", 99) - - ctx.add_label(["flow", "node1"]) - assert backward(99)(ctx, pipeline) == ("flow", "node3", 99) - assert backward(99, cyclicality_flag=False)(ctx, pipeline) == ("service", "fallback", 99) - ctx = Context() - ctx.add_label(["flow", "node2"]) - pipeline = Pipeline( - script={"flow": {"node1": {}}, "service": {"start": {}, "fallback": {}}}, - start_label=("service", "start"), - fallback_label=("service", "fallback"), - ) - assert forward()(ctx, pipeline) == ("service", "fallback", 1.0) diff --git a/tests/script/responses/__init__.py b/tests/script/responses/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/script/responses/test_responses.py b/tests/script/responses/test_responses.py deleted file mode 100644 index c281a9b31..000000000 --- a/tests/script/responses/test_responses.py +++ /dev/null @@ -1,11 +0,0 @@ -# %% -from chatsky.pipeline import Pipeline -from chatsky.script import Context -from chatsky.script.responses import choice - - -def test_response(): - ctx = Context() - pipeline = Pipeline(script={"flow": {"node": {}}}, start_label=("flow", "node")) - for _ in range(10): - assert choice(["text1", "text2"])(ctx, pipeline) in ["text1", "text2"] diff --git a/tests/slots/conftest.py b/tests/slots/conftest.py index 8539b013a..9cdcc4eec 100644 --- a/tests/slots/conftest.py +++ b/tests/slots/conftest.py @@ -1,8 +1,6 @@ import pytest -from chatsky.script import Message, TRANSITIONS, RESPONSE, Context -from chatsky.script import conditions as cnd -from chatsky.pipeline import Pipeline +from chatsky.core import Message, TRANSITIONS, RESPONSE, Context, Pipeline, Transition as Tr from chatsky.slots.slots import SlotNotExtracted @@ -16,13 +14,14 @@ def patch_exception_equality(monkeypatch): @pytest.fixture(scope="function") def pipeline(): - script = {"flow": {"node": {RESPONSE: Message(), TRANSITIONS: {"node": cnd.true()}}}} + script = {"flow": {"node": {RESPONSE: Message(), TRANSITIONS: [Tr(dst="node")]}}} pipeline = Pipeline(script=script, start_label=("flow", "node")) return pipeline @pytest.fixture(scope="function") -def context(): - ctx = Context() +def context(pipeline): + ctx = Context.init(("flow", "node")) ctx.add_request(Message(text="Hi")) + ctx.framework_data.pipeline = pipeline return ctx diff --git a/tests/slots/test_slot_functions.py b/tests/slots/test_slot_functions.py new file mode 100644 index 000000000..b0c80a651 --- /dev/null +++ b/tests/slots/test_slot_functions.py @@ -0,0 +1,147 @@ +from typing import Union, Any +import logging + +import pytest + +from chatsky import Context +from chatsky.core import BaseResponse, Node +from chatsky.core.message import MessageInitTypes, Message +from chatsky.slots.slots import ValueSlot, SlotNotExtracted, GroupSlot, SlotManager +from chatsky import conditions as cnd, responses as rsp, processing as proc +from chatsky.processing.slots import logger as proc_logger +from chatsky.slots.slots import logger as slot_logger +from chatsky.responses.slots import logger as rsp_logger + + +class MsgLen(ValueSlot): + offset: int = 0 + exception: bool = False + + def extract_value(self, ctx: Context) -> Union[Any, SlotNotExtracted]: + if self.exception: + raise RuntimeError() + return len(ctx.last_request.text) + self.offset + + +@pytest.fixture +def root_slot(): + return GroupSlot.model_validate({"0": MsgLen(offset=0), "1": MsgLen(offset=1), "err": MsgLen(exception=True)}) + + +@pytest.fixture +def context(root_slot): + ctx = Context.init(("", "")) + ctx.add_request("text") + ctx.framework_data.slot_manager = SlotManager() + ctx.framework_data.slot_manager.set_root_slot(root_slot) + return ctx + + +@pytest.fixture +def manager(context): + return context.framework_data.slot_manager + + +@pytest.fixture +def call_logger_factory(): + def inner(): + logs = [] + + def func(*args, **kwargs): + logs.append({"args": args, "kwargs": kwargs}) + + return logs, func + + return inner + + +async def test_basic_functions(context, manager, log_event_catcher): + proc_logs = log_event_catcher(proc_logger, level=logging.ERROR) + slot_logs = log_event_catcher(slot_logger, level=logging.ERROR) + + await proc.Extract("0", "2", "err").wrapped_call(context) + + assert manager.get_extracted_slot("0").value == 4 + assert manager.is_slot_extracted("1") is False + assert isinstance(manager.get_extracted_slot("err").extracted_value, RuntimeError) + + assert len(proc_logs) == 1 + assert len(slot_logs) == 1 + + assert await cnd.SlotsExtracted("0", "1", mode="any").wrapped_call(context) is True + assert await cnd.SlotsExtracted("0", "1", mode="all").wrapped_call(context) is False + assert await cnd.SlotsExtracted("0", mode="all").wrapped_call(context) is True + + await proc.Unset("2", "0", "1").wrapped_call(context) + assert manager.is_slot_extracted("0") is False + assert manager.is_slot_extracted("1") is False + assert isinstance(manager.get_extracted_slot("err").extracted_value, RuntimeError) + + assert len(proc_logs) == 2 + + assert await cnd.SlotsExtracted("0", "1", mode="any").wrapped_call(context) is False + + +async def test_extract_all(context, manager, monkeypatch, call_logger_factory): + logs, func = call_logger_factory() + + monkeypatch.setattr(SlotManager, "extract_all", func) + + await proc.ExtractAll().wrapped_call(context) + + assert logs == [{"args": (manager, context), "kwargs": {}}] + + +async def test_unset_all(context, manager, monkeypatch, call_logger_factory): + logs, func = call_logger_factory() + + monkeypatch.setattr(SlotManager, "unset_all_slots", func) + + await proc.UnsetAll().wrapped_call(context) + + assert logs == [{"args": (manager,), "kwargs": {}}] + + +class TestTemplateFilling: + async def test_failed_template(self, context, call_logger_factory): + class MyResponse(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + raise RuntimeError() + + with pytest.raises(RuntimeError): + await rsp.FilledTemplate(MyResponse())(context) + + async def test_missing_text(self, context, log_event_catcher): + logs = log_event_catcher(rsp_logger, level=logging.WARN) + + assert await rsp.FilledTemplate({}).wrapped_call(context) == Message() + assert len(logs) == 1 + + async def test_normal_execution(self, context, manager): + await manager.extract_all(context) + + template_message = Message(text="{0} {1}") + assert await rsp.FilledTemplate(template_message).wrapped_call(context) == Message(text="4 5") + assert template_message.text == "{0} {1}" + + @pytest.mark.parametrize( + "on_exception,result", [("return_none", Message()), ("keep_template", Message(text="{0} {1} {2}"))] + ) + async def test_on_exception(self, context, manager, on_exception, result): + await manager.extract_all(context) + + assert await rsp.FilledTemplate("{0} {1} {2}", on_exception=on_exception).wrapped_call(context) == result + + async def test_fill_template_proc_empty(self, context): + context.framework_data.current_node = Node() + + await proc.FillTemplate().wrapped_call(context) + + assert context.current_node.response is None + + async def test_fill_template_proc(self, context): + context.framework_data.current_node = Node(response="text") + + await proc.FillTemplate().wrapped_call(context) + + assert context.current_node.response == rsp.FilledTemplate(template=Message(text="text")) diff --git a/tests/slots/test_slot_manager.py b/tests/slots/test_slot_manager.py index 22ff3dd48..b6b3f6db1 100644 --- a/tests/slots/test_slot_manager.py +++ b/tests/slots/test_slot_manager.py @@ -9,7 +9,7 @@ ExtractedValueSlot, SlotNotExtracted, ) -from chatsky.script import Message +from chatsky.core import Message, Context def faulty_func(_): @@ -89,174 +89,271 @@ def faulty_func(_): ) -class TestSlotManager: - @pytest.fixture(scope="function") - def context_with_request(self, context): - new_ctx = context.model_copy(deep=True) - new_ctx.add_request(Message(text="I am Bot. My email is bot@bot")) - return new_ctx - - async def test_init_slot_storage(self): - assert root_slot.init_value() == init_slot_storage - - @pytest.fixture(scope="function") - def empty_slot_manager(self): - manager = SlotManager() - manager.set_root_slot(root_slot) - return manager - - @pytest.fixture(scope="function") - def extracted_slot_manager(self): - slot_storage = full_slot_storage.model_copy(deep=True) - return SlotManager(root_slot=root_slot, slot_storage=slot_storage) - - @pytest.fixture(scope="function") - def fully_extracted_slot_manager(self): - slot_storage = full_slot_storage.model_copy(deep=True) - slot_storage.person.surname = ExtractedValueSlot.model_construct( - extracted_value="Bot", is_slot_extracted=True, default_value=None - ) - return SlotManager(root_slot=root_slot, slot_storage=slot_storage) - - def test_get_slot_by_name(self, empty_slot_manager): - assert empty_slot_manager.get_slot("person.name").regexp == r"(?<=am ).+?(?=\.)" - assert empty_slot_manager.get_slot("person.email").regexp == r"[a-zA-Z\.]+@[a-zA-Z\.]+" - assert isinstance(empty_slot_manager.get_slot("person"), GroupSlot) - assert isinstance(empty_slot_manager.get_slot("msg_len"), FunctionSlot) - - with pytest.raises(KeyError): - empty_slot_manager.get_slot("person.birthday") - - with pytest.raises(KeyError): - empty_slot_manager.get_slot("intent") - - @pytest.mark.parametrize( - "slot_name,expected_slot_storage", - [ - ( - "person.name", - ExtractedGroupSlot( - person=ExtractedGroupSlot( - name=extracted_slot_values["person.name"], - surname=init_value_slot, - email=init_value_slot, - ), - msg_len=init_value_slot, +@pytest.fixture(scope="function") +def context_with_request(context): + new_ctx = context.model_copy(deep=True) + new_ctx.add_request(Message(text="I am Bot. My email is bot@bot")) + return new_ctx + + +async def test_init_slot_storage(): + assert root_slot.init_value() == init_slot_storage + + +@pytest.fixture(scope="function") +def empty_slot_manager(): + manager = SlotManager() + manager.set_root_slot(root_slot) + return manager + + +@pytest.fixture(scope="function") +def extracted_slot_manager(): + slot_storage = full_slot_storage.model_copy(deep=True) + return SlotManager(root_slot=root_slot, slot_storage=slot_storage) + + +@pytest.fixture(scope="function") +def fully_extracted_slot_manager(): + slot_storage = full_slot_storage.model_copy(deep=True) + slot_storage.person.surname = ExtractedValueSlot.model_construct( + extracted_value="Bot", is_slot_extracted=True, default_value=None + ) + return SlotManager(root_slot=root_slot, slot_storage=slot_storage) + + +def test_get_slot_by_name(empty_slot_manager): + assert empty_slot_manager.get_slot("person.name").regexp == r"(?<=am ).+?(?=\.)" + assert empty_slot_manager.get_slot("person.email").regexp == r"[a-zA-Z\.]+@[a-zA-Z\.]+" + assert isinstance(empty_slot_manager.get_slot("person"), GroupSlot) + assert isinstance(empty_slot_manager.get_slot("msg_len"), FunctionSlot) + + with pytest.raises(KeyError): + empty_slot_manager.get_slot("person.birthday") + + with pytest.raises(KeyError): + empty_slot_manager.get_slot("intent") + + +@pytest.mark.parametrize( + "slot_name,expected_slot_storage", + [ + ( + "person.name", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=extracted_slot_values["person.name"], + surname=init_value_slot, + email=init_value_slot, ), + msg_len=init_value_slot, ), - ( - "person", - ExtractedGroupSlot( - person=ExtractedGroupSlot( - name=extracted_slot_values["person.name"], - surname=extracted_slot_values["person.surname"], - email=extracted_slot_values["person.email"], - ), - msg_len=init_value_slot, + ), + ( + "person", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=extracted_slot_values["person.name"], + surname=extracted_slot_values["person.surname"], + email=extracted_slot_values["person.email"], ), + msg_len=init_value_slot, ), - ( - "msg_len", - ExtractedGroupSlot( - person=ExtractedGroupSlot( - name=init_value_slot, - surname=init_value_slot, - email=init_value_slot, - ), - msg_len=extracted_slot_values["msg_len"], + ), + ( + "msg_len", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=init_value_slot, + surname=init_value_slot, + email=init_value_slot, ), + msg_len=extracted_slot_values["msg_len"], ), - ], - ) - async def test_slot_extraction( - self, slot_name, expected_slot_storage, empty_slot_manager, context_with_request, pipeline - ): - await empty_slot_manager.extract_slot(slot_name, context_with_request, pipeline) - assert empty_slot_manager.slot_storage == expected_slot_storage - - async def test_extract_all(self, empty_slot_manager, context_with_request, pipeline): - await empty_slot_manager.extract_all(context_with_request, pipeline) - assert empty_slot_manager.slot_storage == full_slot_storage - - @pytest.mark.parametrize( - "slot_name, expected_slot_storage", - [ - ( - "person.name", - ExtractedGroupSlot( - person=ExtractedGroupSlot( - name=unset_slot, - surname=extracted_slot_values["person.surname"], - email=extracted_slot_values["person.email"], - ), - msg_len=extracted_slot_values["msg_len"], + ), + ], +) +async def test_slot_extraction(slot_name, expected_slot_storage, empty_slot_manager, context_with_request): + await empty_slot_manager.extract_slot(slot_name, context_with_request) + assert empty_slot_manager.slot_storage == expected_slot_storage + + +async def test_extract_all(empty_slot_manager, context_with_request): + await empty_slot_manager.extract_all(context_with_request) + assert empty_slot_manager.slot_storage == full_slot_storage + + +@pytest.mark.parametrize( + "slot_name, expected_slot_storage", + [ + ( + "person.name", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=unset_slot, + surname=extracted_slot_values["person.surname"], + email=extracted_slot_values["person.email"], ), + msg_len=extracted_slot_values["msg_len"], ), - ( - "person", - ExtractedGroupSlot( - person=ExtractedGroupSlot( - name=unset_slot, - surname=unset_slot, - email=unset_slot, - ), - msg_len=extracted_slot_values["msg_len"], + ), + ( + "person", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=unset_slot, + surname=unset_slot, + email=unset_slot, ), + msg_len=extracted_slot_values["msg_len"], ), - ( - "msg_len", - ExtractedGroupSlot( - person=ExtractedGroupSlot( - name=extracted_slot_values["person.name"], - surname=extracted_slot_values["person.surname"], - email=extracted_slot_values["person.email"], - ), - msg_len=unset_slot, + ), + ( + "msg_len", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=extracted_slot_values["person.name"], + surname=extracted_slot_values["person.surname"], + email=extracted_slot_values["person.email"], ), + msg_len=unset_slot, ), - ], + ), + ], +) +def test_unset_slot(extracted_slot_manager, slot_name, expected_slot_storage): + extracted_slot_manager.unset_slot(slot_name) + assert extracted_slot_manager.slot_storage == expected_slot_storage + + +def test_unset_all(extracted_slot_manager): + extracted_slot_manager.unset_all_slots() + assert extracted_slot_manager.slot_storage == unset_slot_storage + + +@pytest.mark.parametrize("slot_name", ["person.name", "person", "msg_len"]) +def test_get_extracted_slot(extracted_slot_manager, slot_name): + assert extracted_slot_manager.get_extracted_slot(slot_name) == extracted_slot_values[slot_name] + + +def test_get_extracted_slot_raises(extracted_slot_manager): + with pytest.raises(KeyError): + extracted_slot_manager.get_extracted_slot("none.none") + + with pytest.raises(KeyError): + extracted_slot_manager.get_extracted_slot("person.none") + + with pytest.raises(KeyError): + extracted_slot_manager.get_extracted_slot("person.name.none") + + with pytest.raises(KeyError): + extracted_slot_manager.get_extracted_slot("none") + + +def test_slot_extracted(fully_extracted_slot_manager, empty_slot_manager): + assert fully_extracted_slot_manager.is_slot_extracted("person.name") is True + assert fully_extracted_slot_manager.is_slot_extracted("person") is True + with pytest.raises(KeyError): + fully_extracted_slot_manager.is_slot_extracted("none") + assert fully_extracted_slot_manager.all_slots_extracted() is True + + assert empty_slot_manager.is_slot_extracted("person.name") is False + assert empty_slot_manager.is_slot_extracted("person") is False + with pytest.raises(KeyError): + empty_slot_manager.is_slot_extracted("none") + assert empty_slot_manager.all_slots_extracted() is False + + +@pytest.mark.parametrize( + "template,filled_value", + [ + ( + "Your name is {person.name} {person.surname}, your email: {person.email}.", + "Your name is Bot None, your email: bot@bot.", + ), + ], +) +def test_template_filling(extracted_slot_manager, template, filled_value): + assert extracted_slot_manager.fill_template(template) == filled_value + + +def test_serializable(): + serialized = full_slot_storage.model_dump_json() + assert full_slot_storage == ExtractedGroupSlot.model_validate_json(serialized) + + +async def test_old_slot_storage_update(): + ctx = Context(requests={0: Message(text="text")}) + + slot1 = FunctionSlot(func=lambda msg: len(msg.text) + 2, default_value="1") + init_slot1 = slot1.init_value() + extracted_value1 = await slot1.get_value(ctx) + assert extracted_value1.value == 6 + + slot2 = FunctionSlot(func=lambda msg: len(msg.text) + 3, default_value="2") + init_slot2 = slot2.init_value() + extracted_value2 = await slot2.get_value(ctx) + assert extracted_value2.value == 7 + + old_group_slot = GroupSlot.model_validate( + { + "0": {"0": slot1, "1": slot2}, + "1": {"0": slot1, "1": slot2}, + "2": {"0": slot1, "1": slot2}, + "3": slot1, + "4": slot1, + "5": slot1, + } ) - def test_unset_slot(self, extracted_slot_manager, slot_name, expected_slot_storage): - extracted_slot_manager.unset_slot(slot_name) - assert extracted_slot_manager.slot_storage == expected_slot_storage - - def test_unset_all(self, extracted_slot_manager): - extracted_slot_manager.unset_all_slots() - assert extracted_slot_manager.slot_storage == unset_slot_storage - - @pytest.mark.parametrize("slot_name", ["person.name", "person", "msg_len"]) - def test_get_extracted_slot(self, extracted_slot_manager, slot_name): - assert extracted_slot_manager.get_extracted_slot(slot_name) == extracted_slot_values[slot_name] - - def test_get_extracted_slot_raises(self, extracted_slot_manager): - with pytest.raises(KeyError): - extracted_slot_manager.get_extracted_slot("none") - - def test_slot_extracted(self, fully_extracted_slot_manager, empty_slot_manager): - assert fully_extracted_slot_manager.is_slot_extracted("person.name") is True - assert fully_extracted_slot_manager.is_slot_extracted("person") is True - with pytest.raises(KeyError): - fully_extracted_slot_manager.is_slot_extracted("none") - assert fully_extracted_slot_manager.all_slots_extracted() is True - - assert empty_slot_manager.is_slot_extracted("person.name") is False - assert empty_slot_manager.is_slot_extracted("person") is False - with pytest.raises(KeyError): - empty_slot_manager.is_slot_extracted("none") - assert empty_slot_manager.all_slots_extracted() is False - - @pytest.mark.parametrize( - "template,filled_value", - [ - ( - "Your name is {person.name} {person.surname}, your email: {person.email}.", - "Your name is Bot None, your email: bot@bot.", - ), - ], + + manager = SlotManager() + manager.set_root_slot(old_group_slot) + + assert manager.slot_storage == ExtractedGroupSlot.model_validate( + { + "0": {"0": init_slot1, "1": init_slot2}, + "1": {"0": init_slot1, "1": init_slot2}, + "2": {"0": init_slot1, "1": init_slot2}, + "3": init_slot1, + "4": init_slot1, + "5": init_slot1, + } ) - def test_template_filling(self, extracted_slot_manager, template, filled_value): - assert extracted_slot_manager.fill_template(template) == filled_value - def test_serializable(self): - serialized = full_slot_storage.model_dump_json() - assert full_slot_storage == ExtractedGroupSlot.model_validate_json(serialized) + await manager.extract_all(ctx) + assert manager.slot_storage == ExtractedGroupSlot.model_validate( + { + "0": {"0": extracted_value1, "1": extracted_value2}, + "1": {"0": extracted_value1, "1": extracted_value2}, + "2": {"0": extracted_value1, "1": extracted_value2}, + "3": extracted_value1, + "4": extracted_value1, + "5": extracted_value1, + } + ) + + new_group_slot = GroupSlot.model_validate( + { + "-1": {"0": slot1, "2": slot2}, # added + "0": {"0": slot1, "2": slot2}, + "1": slot2, # type changed + # "2" -- removed + "3": slot2, + "4": {"0": slot1, "2": slot2}, # type changed + # "5" -- removed + "6": slot2, # added + } + ) + + manager.set_root_slot(new_group_slot) + + assert manager.slot_storage == ExtractedGroupSlot.model_validate( + { + "-1": {"0": init_slot1, "2": init_slot2}, + "0": {"0": extracted_value1, "2": init_slot2}, + "1": init_slot2, + "3": extracted_value1, + "4": {"0": init_slot1, "2": init_slot2}, + "6": init_slot2, + } + ) diff --git a/tests/slots/test_slot_types.py b/tests/slots/test_slot_types.py index 159df6224..a21cbd896 100644 --- a/tests/slots/test_slot_types.py +++ b/tests/slots/test_slot_types.py @@ -1,7 +1,7 @@ import pytest from pydantic import ValidationError -from chatsky.script import Message +from chatsky.core import Message from chatsky.slots.slots import ( RegexpSlot, GroupSlot, @@ -35,10 +35,10 @@ ), ], ) -async def test_regexp(user_request, regexp, expected, context, pipeline): +async def test_regexp(user_request, regexp, expected, context): context.add_request(user_request) slot = RegexpSlot(regexp=regexp) - result = await slot.get_value(context, pipeline) + result = await slot.get_value(context) assert result == expected @@ -57,26 +57,26 @@ async def test_regexp(user_request, regexp, expected, context, pipeline): ), ], ) -async def test_function(user_request, func, expected, context, pipeline): +async def test_function(user_request, func, expected, context): context.add_request(user_request) slot = FunctionSlot(func=func) - result = await slot.get_value(context, pipeline) + result = await slot.get_value(context) assert result == expected async def async_func(*args, **kwargs): return func(*args, **kwargs) slot = FunctionSlot(func=async_func) - result = await slot.get_value(context, pipeline) + result = await slot.get_value(context) assert result == expected -async def test_function_exception(context, pipeline): +async def test_function_exception(context): def func(msg: Message): raise RuntimeError("error") slot = FunctionSlot(func=func) - result = await slot.get_value(context, pipeline) + result = await slot.get_value(context) assert result.is_slot_extracted is False assert isinstance(result.extracted_value, RuntimeError) @@ -124,9 +124,9 @@ def func(msg: Message): ), ], ) -async def test_group_slot_extraction(user_request, slot, expected, is_extracted, context, pipeline): +async def test_group_slot_extraction(user_request, slot, expected, is_extracted, context): context.add_request(user_request) - result = await slot.get_value(context, pipeline) + result = await slot.get_value(context) assert result == expected assert result.__slot_extracted__ == is_extracted @@ -159,3 +159,26 @@ async def test_str_representation(): ) == "{'first_name': 'Tom', 'last_name': 'Smith'}" ) + + +class UnserializableClass: + def __init__(self): + self.exc = RuntimeError("exception") + + def __eq__(self, other): + if not isinstance(other, UnserializableClass): + return False + return type(self.exc) == type(other.exc) and self.exc.args == other.exc.args # noqa: E721 + + +async def test_serialization(): + extracted_slot = ExtractedValueSlot.model_construct( + is_slot_extracted=True, extracted_value=UnserializableClass(), default_value=UnserializableClass() + ) + serialized = extracted_slot.model_dump_json() + validated = ExtractedValueSlot.model_validate_json(serialized) + assert extracted_slot == validated + + dump = extracted_slot.model_dump(mode="json") + assert isinstance(dump["extracted_value"], str) + assert isinstance(dump["default_value"], str) diff --git a/tests/slots/test_tutorials.py b/tests/slots/test_tutorials.py deleted file mode 100644 index c0db0587b..000000000 --- a/tests/slots/test_tutorials.py +++ /dev/null @@ -1,20 +0,0 @@ -import importlib -import pytest -from tests.test_utils import get_path_from_tests_to_current_dir -from chatsky.utils.testing.common import check_happy_path - - -dot_path_to_addon = get_path_from_tests_to_current_dir(__file__, separator=".") - - -@pytest.mark.parametrize( - "tutorial_module_name", - [ - "1_basic_example", - ], -) -def test_examples(tutorial_module_name): - module = importlib.import_module(f"tutorials.{dot_path_to_addon}.{tutorial_module_name}") - pipeline = getattr(module, "pipeline") - happy_path = getattr(module, "HAPPY_PATH") - check_happy_path(pipeline, happy_path) diff --git a/tests/stats/test_defaults.py b/tests/stats/test_defaults.py index a770fdc5a..062481bc7 100644 --- a/tests/stats/test_defaults.py +++ b/tests/stats/test_defaults.py @@ -2,9 +2,8 @@ import pytest -from chatsky.script import Context -from chatsky.pipeline import Pipeline -from chatsky.pipeline.types import ExtraHandlerRuntimeInfo, ServiceRuntimeInfo +from chatsky.core import Context, Pipeline +from chatsky.core.service.types import ExtraHandlerRuntimeInfo, ServiceRuntimeInfo try: from chatsky.stats import default_extractors @@ -12,15 +11,8 @@ pytest.skip(allow_module_level=True, reason="One of the Opentelemetry packages is missing.") -@pytest.mark.asyncio -@pytest.mark.parametrize( - "context,expected", - [ - (Context(), {"flow": "greeting_flow", "label": "greeting_flow: start_node", "node": "start_node"}), - (Context(labels={0: ("a", "b")}), {"flow": "a", "node": "b", "label": "a: b"}), - ], -) -async def test_get_current_label(context: Context, expected: set): +async def test_get_current_label(): + context = Context.init(("a", "b")) pipeline = Pipeline(script={"greeting_flow": {"start_node": {}}}, start_label=("greeting_flow", "start_node")) runtime_info = ExtraHandlerRuntimeInfo( func=lambda x: x, @@ -30,18 +22,10 @@ async def test_get_current_label(context: Context, expected: set): ), ) result = await default_extractors.get_current_label(context, pipeline, runtime_info) - assert result == expected + assert result == {"flow": "a", "node": "b", "label": "a: b"} -@pytest.mark.asyncio -@pytest.mark.parametrize( - "context", - [ - Context(), - Context(labels={0: ("a", "b")}), - ], -) -async def test_otlp_integration(context, tracer_exporter_and_provider, log_exporter_and_provider): +async def test_otlp_integration(tracer_exporter_and_provider, log_exporter_and_provider): _, tracer_provider = tracer_exporter_and_provider log_exporter, logger_provider = log_exporter_and_provider tutorial_module = importlib.import_module("tutorials.stats.1_extractor_functions") @@ -54,7 +38,7 @@ async def test_otlp_integration(context, tracer_exporter_and_provider, log_expor path=".", name=".", timeout=None, asynchronous=False, execution_state={".": "FINISHED"} ), ) - _ = await default_extractors.get_current_label(context, tutorial_module.pipeline, runtime_info) + _ = await default_extractors.get_current_label(Context.init(("a", "b")), tutorial_module.pipeline, runtime_info) tracer_provider.force_flush() logger_provider.force_flush() assert len(log_exporter.get_finished_logs()) > 0 diff --git a/tests/tutorials/test_utils.py b/tests/tutorials/test_utils.py index b138ee453..5bdf1efcd 100644 --- a/tests/tutorials/test_utils.py +++ b/tests/tutorials/test_utils.py @@ -1,5 +1,4 @@ import os -import re import pytest from chatsky.utils.testing.common import check_happy_path, is_interactive_mode @@ -7,12 +6,8 @@ def test_unhappy_path(): - with pytest.raises(Exception) as e: + with pytest.raises(AssertionError): check_happy_path(pipeline, (("Hi", "false_response"),)) - assert e - msg = str(e) - assert msg - assert re.search(r"pipeline.+", msg) def test_is_interactive(): diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 21e4cd501..89444b064 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -11,7 +11,7 @@ import chatsky.utils.db_benchmark as bm from chatsky.utils.db_benchmark.basic_config import get_context, get_dict from chatsky.context_storages import JSONContextStorage - from chatsky.script import Context, Message + from chatsky.core import Context, Message except ImportError: pytest.skip(reason="`chatsky[benchmark,tests]` not installed", allow_module_level=True) diff --git a/tests/utils/test_serialization.py b/tests/utils/test_serialization.py index e8b72f022..7765f4d3d 100644 --- a/tests/utils/test_serialization.py +++ b/tests/utils/test_serialization.py @@ -1,7 +1,7 @@ -from typing import Optional +from typing import Optional, Dict, Any import pytest -from pydantic import BaseModel +from pydantic import BaseModel, field_serializer, field_validator from copy import deepcopy import chatsky.utils.devel.json_serialization as json_ser @@ -80,7 +80,20 @@ def test_json_pickle(self, unserializable_dict, non_serializable_fields, deseria def test_serializable_value(self, unserializable_obj): class Class(BaseModel): - field: Optional[json_ser.PickleEncodedValue] = None + field: Optional[Any] = None + + @field_serializer("field", when_used="json") + def pickle_serialize_field(self, value): + if value is not None: + return json_ser.pickle_serializer(value) + return value + + @field_validator("field", mode="before") + @classmethod + def pickle_validate_field(cls, value): + if value is not None: + return json_ser.pickle_validator(value) + return value obj = Class() obj.field = unserializable_obj @@ -99,7 +112,20 @@ class Class(BaseModel): def test_serializable_dict(self, unserializable_dict, non_serializable_fields, deserialized_dict): class Class(BaseModel): - field: json_ser.JSONSerializableDict + field: Optional[Dict[str, Any]] = None + + @field_serializer("field", when_used="json") + def pickle_serialize_dicts(self, value): + if isinstance(value, dict): + return json_ser.json_pickle_serializer(value) + return value + + @field_validator("field", mode="before") + @classmethod + def pickle_validate_dicts(cls, value): + if isinstance(value, dict): + return json_ser.json_pickle_validator(value) + return value obj = Class(field=unserializable_dict) diff --git a/tutorials/context_storages/1_basics.py b/tutorials/context_storages/1_basics.py index 4ea9c9d51..4dbc2335a 100644 --- a/tutorials/context_storages/1_basics.py +++ b/tutorials/context_storages/1_basics.py @@ -17,11 +17,10 @@ from chatsky.context_storages import context_storage_factory -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH @@ -33,10 +32,8 @@ pipeline = Pipeline(**TOY_SCRIPT_KWARGS, context_storage=db) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) # a function for automatic tutorial running (testing) with HAPPY_PATH - # This runs tutorial in interactive mode if not in IPython env - # and if `DISABLE_INTERACTIVE_MODE` is not set if is_interactive_mode(): - run_interactive_mode(pipeline) # This runs tutorial in interactive mode + pipeline.run() diff --git a/tutorials/context_storages/2_postgresql.py b/tutorials/context_storages/2_postgresql.py index 4124c4799..f13fc95b4 100644 --- a/tutorials/context_storages/2_postgresql.py +++ b/tutorials/context_storages/2_postgresql.py @@ -19,11 +19,10 @@ from chatsky.context_storages import context_storage_factory -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH @@ -42,6 +41,6 @@ # %% if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/context_storages/3_mongodb.py b/tutorials/context_storages/3_mongodb.py index 0b2919e73..3bb80c53c 100644 --- a/tutorials/context_storages/3_mongodb.py +++ b/tutorials/context_storages/3_mongodb.py @@ -18,11 +18,10 @@ from chatsky.context_storages import context_storage_factory -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH @@ -40,6 +39,6 @@ # %% if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/context_storages/4_redis.py b/tutorials/context_storages/4_redis.py index 5fe215ddf..5325d2fb4 100644 --- a/tutorials/context_storages/4_redis.py +++ b/tutorials/context_storages/4_redis.py @@ -18,11 +18,10 @@ from chatsky.context_storages import context_storage_factory -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH @@ -39,6 +38,6 @@ # %% if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/context_storages/5_mysql.py b/tutorials/context_storages/5_mysql.py index b9f9d01a0..8c61248b8 100644 --- a/tutorials/context_storages/5_mysql.py +++ b/tutorials/context_storages/5_mysql.py @@ -19,11 +19,10 @@ from chatsky.context_storages import context_storage_factory -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH @@ -42,6 +41,6 @@ # %% if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/context_storages/6_sqlite.py b/tutorials/context_storages/6_sqlite.py index b9e8ea8a9..6ec4ee931 100644 --- a/tutorials/context_storages/6_sqlite.py +++ b/tutorials/context_storages/6_sqlite.py @@ -22,11 +22,10 @@ from chatsky.context_storages import context_storage_factory -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH @@ -46,6 +45,6 @@ # %% if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/context_storages/7_yandex_database.py b/tutorials/context_storages/7_yandex_database.py index be5ac7a69..19c3b4a72 100644 --- a/tutorials/context_storages/7_yandex_database.py +++ b/tutorials/context_storages/7_yandex_database.py @@ -18,10 +18,9 @@ from chatsky.context_storages import context_storage_factory -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, - run_interactive_mode, is_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH @@ -47,6 +46,6 @@ # %% if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/messengers/telegram/1_basic.py b/tutorials/messengers/telegram/1_basic.py index a7379a91d..dec0ea10e 100644 --- a/tutorials/messengers/telegram/1_basic.py +++ b/tutorials/messengers/telegram/1_basic.py @@ -17,11 +17,15 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) # %% import os -from chatsky.script import conditions as cnd -from chatsky.script import labels as lbl -from chatsky.script import RESPONSE, TRANSITIONS, Message +from chatsky import ( + RESPONSE, + TRANSITIONS, + Pipeline, + Transition as Tr, + conditions as cnd, + destinations as dst, +) from chatsky.messengers.telegram import LongpollingInterface -from chatsky.pipeline import Pipeline from chatsky.utils.testing.common import is_interactive_mode @@ -51,15 +55,19 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) script = { "greeting_flow": { "start_node": { - TRANSITIONS: {"greeting_node": cnd.exact_match("/start")}, + TRANSITIONS: [ + Tr(dst="greeting_node", cnd=cnd.ExactMatch("/start")) + ], }, "greeting_node": { - RESPONSE: Message("Hi"), - TRANSITIONS: {lbl.repeat(): cnd.true()}, + RESPONSE: "Hi", + TRANSITIONS: [Tr(dst=dst.Current())], }, "fallback_node": { - RESPONSE: Message("Please, repeat the request"), - TRANSITIONS: {"greeting_node": cnd.exact_match("/start")}, + RESPONSE: "Please, repeat the request", + TRANSITIONS: [ + Tr(dst="greeting_node", cnd=cnd.ExactMatch("/start")) + ], }, } } diff --git a/tutorials/messengers/telegram/2_attachments.py b/tutorials/messengers/telegram/2_attachments.py index 461710169..68a135c70 100644 --- a/tutorials/messengers/telegram/2_attachments.py +++ b/tutorials/messengers/telegram/2_attachments.py @@ -19,11 +19,17 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) from pydantic import HttpUrl -from chatsky.script import conditions as cnd -from chatsky.script import GLOBAL, RESPONSE, TRANSITIONS, Message +from chatsky import ( + GLOBAL, + RESPONSE, + TRANSITIONS, + Message, + Pipeline, + Transition as Tr, + conditions as cnd, +) from chatsky.messengers.telegram import LongpollingInterface -from chatsky.pipeline import Pipeline -from chatsky.script.core.message import ( +from chatsky.core.message import ( Animation, Audio, Contact, @@ -131,44 +137,45 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) """ The bot below sends different attachments on request. -[Here](%doclink(api,script.core.message)) you can find +[Here](%doclink(api,core.message)) you can find all the attachment options available. """ # %% script = { GLOBAL: { - TRANSITIONS: { - ("main_flow", f"{attachment}_node"): cnd.exact_match(attachment) + TRANSITIONS: [ + Tr( + dst=("main_flow", f"{attachment}_node"), + cnd=cnd.ExactMatch(attachment), + ) for attachment in ATTACHMENTS - } + ] }, "main_flow": { "start_node": { - TRANSITIONS: {"intro_node": cnd.exact_match("/start")}, + TRANSITIONS: [Tr(dst="intro_node", cnd=cnd.ExactMatch("/start"))], }, "intro_node": { - RESPONSE: Message( - f'Type {", ".join(QUOTED_ATTACHMENTS[:-1])}' - f" or {QUOTED_ATTACHMENTS[-1]}" - f" to receive a corresponding attachment!" - ), + RESPONSE: f'Type {", ".join(QUOTED_ATTACHMENTS[:-1])}' + f" or {QUOTED_ATTACHMENTS[-1]}" + f" to receive a corresponding attachment!", }, "location_node": { RESPONSE: Message( - "Here's your location!", + text="Here's your location!", attachments=[Location(**location_data)], ), }, "contact_node": { RESPONSE: Message( - "Here's your contact!", + text="Here's your contact!", attachments=[Contact(**contact_data)], ), }, "poll_node": { RESPONSE: Message( - "Here's your poll!", + text="Here's your poll!", attachments=[ Poll( question="What is the poll question?", @@ -182,55 +189,55 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) }, "sticker_node": { RESPONSE: Message( - "Here's your sticker!", + text="Here's your sticker!", attachments=[Sticker(**sticker_data)], ), }, "audio_node": { RESPONSE: Message( - "Here's your audio!", + text="Here's your audio!", attachments=[Audio(**audio_data)], ), }, "video_node": { RESPONSE: Message( - "Here's your video!", + text="Here's your video!", attachments=[Video(**video_data)], ), }, "animation_node": { RESPONSE: Message( - "Here's your animation!", + text="Here's your animation!", attachments=[Animation(**animation_data)], ), }, "image_node": { RESPONSE: Message( - "Here's your image!", + text="Here's your image!", attachments=[Image(**image_data)], ), }, "document_node": { RESPONSE: Message( - "Here's your document!", + text="Here's your document!", attachments=[Document(**document_data)], ), }, "voice_message_node": { RESPONSE: Message( - "Here's your voice message!", + text="Here's your voice message!", attachments=[VoiceMessage(source=audio_data["source"])], ), }, "video_message_node": { RESPONSE: Message( - "Here's your video message!", + text="Here's your video message!", attachments=[VideoMessage(source=video_data["source"])], ), }, "media_group_node": { RESPONSE: Message( - "Here's your media group!", + text="Here's your media group!", attachments=[ MediaGroup( group=[ @@ -242,12 +249,10 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) ), }, "fallback_node": { - RESPONSE: Message( - f"Unknown attachment type, try again! " - f"Supported attachments are: " - f'{", ".join(QUOTED_ATTACHMENTS[:-1])} ' - f"and {QUOTED_ATTACHMENTS[-1]}." - ), + RESPONSE: f"Unknown attachment type, try again! " + f"Supported attachments are: " + f'{", ".join(QUOTED_ATTACHMENTS[:-1])} ' + f"and {QUOTED_ATTACHMENTS[-1]}.", }, }, } diff --git a/tutorials/messengers/telegram/3_advanced.py b/tutorials/messengers/telegram/3_advanced.py index bed4da369..76692e406 100644 --- a/tutorials/messengers/telegram/3_advanced.py +++ b/tutorials/messengers/telegram/3_advanced.py @@ -22,18 +22,25 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram.constants import ParseMode -from chatsky.script import conditions as cnd -from chatsky.script import RESPONSE, TRANSITIONS, Message +from chatsky import ( + RESPONSE, + TRANSITIONS, + GLOBAL, + Message, + Pipeline, + BaseResponse, + Context, + Transition as Tr, + conditions as cnd, +) from chatsky.messengers.telegram import LongpollingInterface -from chatsky.pipeline import Pipeline -from chatsky.script.core.context import Context -from chatsky.script.core.keywords import GLOBAL -from chatsky.script.core.message import ( +from chatsky.core.message import ( DataAttachment, Document, Image, Location, Sticker, + MessageInitTypes, ) from chatsky.utils.testing.common import is_interactive_mode @@ -100,37 +107,39 @@ class for information about different arguments # %% -async def hash_data_attachment_request(ctx: Context, pipe: Pipeline) -> Message: - attachment = [ - a for a in ctx.last_request.attachments if isinstance(a, DataAttachment) - ] - if len(attachment) > 0: - attachment_bytes = await attachment[0].get_bytes( - pipe.messenger_interface - ) - attachment_hash = sha256(attachment_bytes).hexdigest() - resp_format = ( - "Here's your previous request first attachment sha256 hash: `{}`!\n" - + "Run /start command again to restart." - ) - return Message( - resp_format.format( +class DataAttachmentHash(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + attachment = [ + a + for a in ctx.last_request.attachments + if isinstance(a, DataAttachment) + ] + if len(attachment) > 0: + attachment_bytes = await attachment[0].get_bytes( + ctx.pipeline.messenger_interface + ) + attachment_hash = sha256(attachment_bytes).hexdigest() + resp_format = ( + "Here's your previous request first attachment sha256 hash: " + "`{}`!\n" + "Run /start command again to restart." + ) + return resp_format.format( attachment_hash, parse_mode=ParseMode.MARKDOWN_V2 ) - ) - else: - return Message( - "Last request did not contain any data attachment!\n" - + "Run /start command again to restart." - ) + else: + return ( + "Last request did not contain any data attachment!\n" + "Run /start command again to restart." + ) # %% script = { GLOBAL: { - TRANSITIONS: { - ("main_flow", "main_node"): cnd.exact_match("/start"), - } + TRANSITIONS: [ + Tr(dst=("main_flow", "main_node"), cnd=cnd.ExactMatch("/start")) + ] }, "main_flow": { "start_node": {}, @@ -184,22 +193,27 @@ async def hash_data_attachment_request(ctx: Context, pipe: Pipeline) -> Message: ), ], ), - TRANSITIONS: { - "formatted_node": cnd.has_callback_query("formatted"), - "attachments_node": cnd.has_callback_query("attachments"), - "secret_node": cnd.has_callback_query("secret"), - "thumbnail_node": cnd.has_callback_query("thumbnail"), - "hash_init_node": cnd.has_callback_query("hash"), - "main_node": cnd.has_callback_query("restart"), - "fallback_node": cnd.has_callback_query("quit"), - }, + TRANSITIONS: [ + Tr(dst="formatted_node", cnd=cnd.HasCallbackQuery("formatted")), + Tr( + dst="attachments_node", + cnd=cnd.HasCallbackQuery("attachments"), + ), + Tr(dst="secret_node", cnd=cnd.HasCallbackQuery("secret")), + Tr(dst="thumbnail_node", cnd=cnd.HasCallbackQuery("thumbnail")), + Tr(dst="hash_init_node", cnd=cnd.HasCallbackQuery("hash")), + Tr(dst="main_node", cnd=cnd.HasCallbackQuery("restart")), + Tr(dst="fallback_node", cnd=cnd.HasCallbackQuery("quit")), + ], }, "formatted_node": { - RESPONSE: Message(formatted_text, parse_mode=ParseMode.MARKDOWN_V2), + RESPONSE: Message( + text=formatted_text, parse_mode=ParseMode.MARKDOWN_V2 + ), }, "attachments_node": { RESPONSE: Message( - "Here's your message with multiple attachments " + text="Here's your message with multiple attachments " + "(a location and a sticker)!\n" + "Run /start command again to restart.", attachments=[ @@ -210,31 +224,31 @@ async def hash_data_attachment_request(ctx: Context, pipe: Pipeline) -> Message: }, "secret_node": { RESPONSE: Message( - "Here's your secret image! " + text="Here's your secret image! " + "Run /start command again to restart.", attachments=[Image(**image_data)], ), }, "thumbnail_node": { RESPONSE: Message( - "Here's your document with tumbnail! " + text="Here's your document with tumbnail! " + "Run /start command again to restart.", attachments=[Document(**document_data)], ), }, "hash_init_node": { RESPONSE: Message( - "Alright! Now send me a message with data attachment " + text="Alright! Now send me a message with data attachment " + "(audio, video, animation, image, sticker or document)!" ), - TRANSITIONS: {"hash_request_node": cnd.true()}, + TRANSITIONS: [Tr(dst="hash_request_node")], }, "hash_request_node": { - RESPONSE: hash_data_attachment_request, + RESPONSE: DataAttachmentHash(), }, "fallback_node": { RESPONSE: Message( - "Bot has entered unrecoverable state:" + text="Bot has entered unrecoverable state:" + "/\nRun /start command again to restart." ), }, diff --git a/tutorials/messengers/web_api_interface/1_fastapi.py b/tutorials/messengers/web_api_interface/1_fastapi.py index ef07009ed..208685123 100644 --- a/tutorials/messengers/web_api_interface/1_fastapi.py +++ b/tutorials/messengers/web_api_interface/1_fastapi.py @@ -10,20 +10,17 @@ Here, %mddoclink(api,messengers.common.interface,CallbackMessengerInterface) is used to process requests. -%mddoclink(api,script.core.message,Message) +%mddoclink(api,core.message,Message) is used in creating a JSON Schema for the endpoint. """ - # %pip install chatsky uvicorn fastapi # %% from chatsky.messengers.common.interface import CallbackMessengerInterface -from chatsky.script import Message -from chatsky.pipeline import Pipeline +from chatsky import Message, Pipeline from chatsky.utils.testing import TOY_SCRIPT_KWARGS, is_interactive_mode import uvicorn -from pydantic import BaseModel from fastapi import FastAPI # %% [markdown] @@ -92,18 +89,13 @@ app = FastAPI() -class Output(BaseModel): - user_id: str - response: Message - - -@app.post("/chat", response_model=Output) +@app.post("/chat", response_model=Message) async def respond( user_id: str, user_message: Message, ): context = await messenger_interface.on_request_async(user_message, user_id) - return {"user_id": user_id, "response": context.last_response} + return context.last_response # %% diff --git a/tutorials/messengers/web_api_interface/2_websocket_chat.py b/tutorials/messengers/web_api_interface/2_websocket_chat.py index af766d1f5..ae5ff5440 100644 --- a/tutorials/messengers/web_api_interface/2_websocket_chat.py +++ b/tutorials/messengers/web_api_interface/2_websocket_chat.py @@ -18,15 +18,14 @@ Here, %mddoclink(api,messengers.common.interface,CallbackMessengerInterface) is used to process requests. -%mddoclink(api,script.core.message,Message) is used to represent text messages. +%mddoclink(api,core.message,Message) is used to represent text messages. """ # %pip install chatsky uvicorn fastapi # %% from chatsky.messengers.common.interface import CallbackMessengerInterface -from chatsky.script import Message -from chatsky.pipeline import Pipeline +from chatsky import Message, Pipeline from chatsky.utils.testing import TOY_SCRIPT_KWARGS, is_interactive_mode import uvicorn @@ -43,8 +42,9 @@ # %% app = FastAPI() +PORT = 8000 -html = """ +html = f""" @@ -60,20 +60,20 @@ @@ -112,5 +112,5 @@ async def websocket_endpoint(websocket: WebSocket, client_id: int): uvicorn.run( app, host="127.0.0.1", - port=8000, + port=PORT, ) diff --git a/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py b/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py index 6f7d832f2..c5e9a6bfa 100644 --- a/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py +++ b/tutorials/messengers/web_api_interface/3_load_testing_with_locust.py @@ -32,6 +32,9 @@ You should see the result at http://127.0.0.1:8089. Make sure that your POST endpoint is also running (run the FastAPI tutorial). + +If using the FastAPI tutorial, set "Host" to `http://127.0.0.1:8000`, +when prompted by Locust. """ @@ -52,7 +55,7 @@ from locust import FastHttpUser, task, constant, main -from chatsky.script import Message +from chatsky import Message from chatsky.utils.testing import HAPPY_PATH, is_interactive_mode @@ -88,6 +91,7 @@ def check_happy_path(self, happy_path): user_id = str(uuid.uuid4()) for request, response in happy_path: + request = Message.model_validate(request) with self.client.post( f"/chat?user_id={user_id}", headers={ @@ -95,12 +99,13 @@ def check_happy_path(self, happy_path): "Content-Type": "application/json", }, # Name is the displayed name of the request. - name=f"/chat?user_message={request.json()}", - data=request.json(), + name=f"/chat?user_message={request.model_dump_json()}", + data=request.model_dump_json(), catch_response=True, ) as candidate_response: + candidate_response.raise_for_status() text_response = Message.model_validate( - candidate_response.json().get("response") + candidate_response.json() ) if response is not None: @@ -108,7 +113,7 @@ def check_happy_path(self, happy_path): error_message = response(text_response) if error_message is not None: candidate_response.failure(error_message) - elif text_response != response: + elif text_response != Message.model_validate(response): candidate_response.failure( f"Expected: {response.model_dump_json()}\n" f"Got: {text_response.model_dump_json()}" @@ -135,11 +140,11 @@ def check_first_message(msg: Message) -> str | None: self.check_happy_path( [ # a function can be used to check the return message - (Message("Hi"), check_first_message), + ("Hi", check_first_message), # a None is used if return message should not be checked - (Message("i'm fine, how are you?"), None), + ("i'm fine, how are you?", None), # this should fail - (Message("Hi"), check_first_message), + ("Hi", check_first_message), ] ) diff --git a/tutorials/messengers/web_api_interface/4_streamlit_chat.py b/tutorials/messengers/web_api_interface/4_streamlit_chat.py index 89fda9827..280160b6c 100644 --- a/tutorials/messengers/web_api_interface/4_streamlit_chat.py +++ b/tutorials/messengers/web_api_interface/4_streamlit_chat.py @@ -40,7 +40,7 @@ import streamlit as st from streamlit_chat import message import streamlit.components.v1 as components -from chatsky.script import Message +from chatsky import Message # %% [markdown] @@ -127,7 +127,7 @@ def send_and_receive(): ) bot_response.raise_for_status() - bot_message = Message.model_validate(bot_response.json()["response"]).text + bot_message = Message.model_validate(bot_response.json()).text # # Implementation without using Message: # bot_response = query( diff --git a/tutorials/pipeline/1_basics.py b/tutorials/pipeline/1_basics.py index 898558d91..2c355230f 100644 --- a/tutorials/pipeline/1_basics.py +++ b/tutorials/pipeline/1_basics.py @@ -6,16 +6,14 @@ module as an extension to `chatsky.script.core`. Here, `__call__` (same as -%mddoclink(api,pipeline.pipeline.pipeline,Pipeline.run)) +%mddoclink(api,core.pipeline,Pipeline.run)) method is used to execute pipeline once. """ # %pip install chatsky # %% -from chatsky.script import Context, Message - -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing import ( check_happy_path, @@ -33,9 +31,10 @@ a pipeline of the most basic structure: "pre-services -> actor -> post-services" as well as to define `context_storage` and `messenger_interface`. -Actor is a component of :py:class:`.Pipeline`, that contains the -:py:class:`.Script` and handles it. It is responsible for processing -user input and determining the appropriate response based on the +Actor is a component of %mddoclink(api,core.pipeline,Pipeline), +that contains the %mddoclink(api,core.script,Script) and handles it. +It is responsible for processing user input and +determining the appropriate response based on the current state of the conversation and the script. These parameters usage will be shown in tutorials 2, 3 and 6. @@ -74,14 +73,10 @@ # %% if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) # a function for automatic tutorial running (testing) with HAPPY_PATH # This runs tutorial in interactive mode if not in IPython env # and if `DISABLE_INTERACTIVE_MODE` is not set if is_interactive_mode(): - ctx_id = 0 # 0 will be current dialog (context) identification. - while True: - message = Message(input("Send request: ")) - ctx: Context = pipeline(message, ctx_id) - print(ctx.last_response) + pipeline.run() diff --git a/tutorials/pipeline/2_pre_and_post_processors.py b/tutorials/pipeline/2_pre_and_post_processors.py index ec45af5be..7fda2ccaf 100644 --- a/tutorials/pipeline/2_pre_and_post_processors.py +++ b/tutorials/pipeline/2_pre_and_post_processors.py @@ -5,7 +5,7 @@ The following tutorial shows more advanced usage of `pipeline` module as an extension to `chatsky.script.core`. -Here, %mddoclink(api,script.core.context,Context.misc) +Here, %mddoclink(api,core.context,Context.misc) dictionary of context is used for storing additional data. """ @@ -15,9 +15,7 @@ import logging from chatsky.messengers.console import CLIMessengerInterface -from chatsky.script import Context, Message - -from chatsky.pipeline import Pipeline +from chatsky import Context, Message, Pipeline from chatsky.utils.testing import ( check_happy_path, @@ -79,7 +77,7 @@ def pong_processor(ctx: Context): if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): ctx_id = 0 # 0 will be current dialog (context) identification. while True: diff --git a/tutorials/pipeline/3_pipeline_dict_with_services_basic.py b/tutorials/pipeline/3_pipeline_dict_with_services_basic.py index f339771a7..9d54da0fc 100644 --- a/tutorials/pipeline/3_pipeline_dict_with_services_basic.py +++ b/tutorials/pipeline/3_pipeline_dict_with_services_basic.py @@ -5,10 +5,10 @@ The following tutorial shows `pipeline` creation from dict and most important pipeline components. -Here, %mddoclink(api,pipeline.service.service,Service) +Here, %mddoclink(api,core.service.service,Service) class, that can be used for pre- and postprocessing of messages is shown. -%mddoclink(api,pipeline.pipeline.pipeline,Pipeline)'s +%mddoclink(api,core.pipeline,Pipeline)'s constructor method is used for pipeline creation (directly or from dictionary). """ @@ -17,12 +17,12 @@ # %% import logging -from chatsky.pipeline import Service, Pipeline +from chatsky import Pipeline +from chatsky.core.service import Service from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -112,6 +112,6 @@ def postprocess(_): if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) # This runs tutorial in interactive mode + pipeline.run() diff --git a/tutorials/pipeline/3_pipeline_dict_with_services_full.py b/tutorials/pipeline/3_pipeline_dict_with_services_full.py index ad17281b0..697b9f12f 100644 --- a/tutorials/pipeline/3_pipeline_dict_with_services_full.py +++ b/tutorials/pipeline/3_pipeline_dict_with_services_full.py @@ -18,13 +18,12 @@ import logging import urllib.request -from chatsky.script import Context +from chatsky import Context, Pipeline from chatsky.messengers.console import CLIMessengerInterface -from chatsky.pipeline import Service, Pipeline, ServiceRuntimeInfo +from chatsky.core.service import Service, ServiceRuntimeInfo from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import TOY_SCRIPT, HAPPY_PATH @@ -128,8 +127,7 @@ def postprocess(ctx: Context, pl: Pipeline): f"resulting misc looks like:" f"{json.dumps(ctx.misc, indent=4, default=str)}" ) - fallback_flow, fallback_node, _ = pl.actor.fallback_label - received_response = pl.script[fallback_flow][fallback_node].response + received_response = pl.script.get_inherited_node(pl.fallback_label).response responses_match = received_response == ctx.last_response logger.info(f"actor is{'' if responses_match else ' not'} in fallback node") @@ -164,6 +162,6 @@ def postprocess(ctx: Context, pl: Pipeline): pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/pipeline/4_groups_and_conditions_basic.py b/tutorials/pipeline/4_groups_and_conditions_basic.py index bd560695f..a35562e22 100644 --- a/tutorials/pipeline/4_groups_and_conditions_basic.py +++ b/tutorials/pipeline/4_groups_and_conditions_basic.py @@ -4,8 +4,8 @@ The following example shows `pipeline` service group usage and start conditions. -Here, %mddoclink(api,pipeline.service.service,Service)s -and %mddoclink(api,pipeline.service.group,ServiceGroup)s +Here, %mddoclink(api,core.service.service,Service)s +and %mddoclink(api,core.service.group,ServiceGroup)s are shown for advanced data pre- and postprocessing based on conditions. """ @@ -15,18 +15,17 @@ import json import logging -from chatsky.pipeline import ( +from chatsky.core.service import ( Service, - Pipeline, not_condition, service_successful_condition, ServiceRuntimeInfo, ) +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -102,7 +101,9 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): Service( handler=never_running_service, start_condition=not_condition( - service_successful_condition(".pipeline.always_running_service") + service_successful_condition( + ".pipeline.pre.always_running_service" + ) # pre services belong to the "pre" group; post -- to "post" ), ), Service( @@ -117,6 +118,6 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/pipeline/4_groups_and_conditions_full.py b/tutorials/pipeline/4_groups_and_conditions_full.py index f185b18be..890231717 100644 --- a/tutorials/pipeline/4_groups_and_conditions_full.py +++ b/tutorials/pipeline/4_groups_and_conditions_full.py @@ -14,20 +14,19 @@ # %% import logging -from chatsky.pipeline import ( +from chatsky.core.service import ( Service, - Pipeline, ServiceGroup, not_condition, service_successful_condition, all_condition, ServiceRuntimeInfo, ) +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -179,7 +178,7 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): simple_service, # This simple service # will be named `simple_service_1` ], # Despite this is the unnamed service group in the root - # service group, it will be named `service_group_0` + # service group, it will be named `pre` as it holds pre services "post_services": [ ServiceGroup( name="named_group", @@ -188,13 +187,13 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): handler=simple_service, start_condition=all_condition( service_successful_condition( - ".pipeline.service_group_0.simple_service_0" + ".pipeline.pre.simple_service_0" ), service_successful_condition( - ".pipeline.service_group_0.simple_service_1" + ".pipeline.pre.simple_service_1" ), ), # Alternative: - # service_successful_condition(".pipeline.service_group_0") + # service_successful_condition(".pipeline.pre") name="running_service", ), # This simple service will be named `running_service`, # because its name is manually overridden @@ -202,11 +201,12 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): handler=never_running_service, start_condition=not_condition( service_successful_condition( - ".pipeline.named_group.running_service" + ".pipeline.post.named_group.running_service" ) ), ), ], + requested_async_flag=False, # forbid services from running in async ), runtime_info_printing_service, ], @@ -218,7 +218,6 @@ def runtime_info_printing_service(_, __, info: ServiceRuntimeInfo): if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - logger.info(f"Pipeline structure:\n{pipeline.pretty_format()}") - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py b/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py index bf82879a4..1af92e6f6 100644 --- a/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py +++ b/tutorials/pipeline/5_asynchronous_groups_and_services_basic.py @@ -5,7 +5,7 @@ The following tutorial shows `pipeline` asynchronous service and service group usage. -Here, %mddoclink(api,pipeline.service.group,ServiceGroup)s +Here, %mddoclink(api,core.service.group,ServiceGroup)s are shown for advanced and asynchronous data pre- and postprocessing. """ @@ -14,12 +14,11 @@ # %% import asyncio -from chatsky.pipeline import Pipeline +from chatsky import Pipeline from chatsky.utils.testing.common import ( is_interactive_mode, check_happy_path, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -57,6 +56,6 @@ async def time_consuming_service(_): pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/pipeline/5_asynchronous_groups_and_services_full.py b/tutorials/pipeline/5_asynchronous_groups_and_services_full.py index ef2dacb32..aebe371d4 100644 --- a/tutorials/pipeline/5_asynchronous_groups_and_services_full.py +++ b/tutorials/pipeline/5_asynchronous_groups_and_services_full.py @@ -19,12 +19,11 @@ import logging import urllib.request -from chatsky.pipeline import ServiceGroup, Pipeline, ServiceRuntimeInfo -from chatsky.script import Context +from chatsky.core.service import ServiceGroup, ServiceRuntimeInfo +from chatsky import Context, Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -147,6 +146,6 @@ def context_printing_service(ctx: Context): pipeline = Pipeline.model_validate(pipeline_dict) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/pipeline/6_extra_handlers_basic.py b/tutorials/pipeline/6_extra_handlers_basic.py index 4de439271..c38e42391 100644 --- a/tutorials/pipeline/6_extra_handlers_basic.py +++ b/tutorials/pipeline/6_extra_handlers_basic.py @@ -4,8 +4,8 @@ The following tutorial shows extra handlers possibilities and use cases. -Here, extra handlers %mddoclink(api,pipeline.service.extra,BeforeHandler) -and %mddoclink(api,pipeline.service.extra,AfterHandler) +Here, extra handlers %mddoclink(api,core.service.extra,BeforeHandler) +and %mddoclink(api,core.service.extra,AfterHandler) are shown as additional means of data processing, attached to services. """ @@ -18,16 +18,14 @@ import random from datetime import datetime -from chatsky.pipeline import ( - Pipeline, +from chatsky.core.service import ( ServiceGroup, ExtraHandlerRuntimeInfo, ) -from chatsky.script import Context +from chatsky import Context, Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -117,6 +115,6 @@ def logging_service(ctx: Context): pipeline = Pipeline(**pipeline_dict) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/pipeline/6_extra_handlers_full.py b/tutorials/pipeline/6_extra_handlers_full.py index a3e2c5a26..6d6b4ecf5 100644 --- a/tutorials/pipeline/6_extra_handlers_full.py +++ b/tutorials/pipeline/6_extra_handlers_full.py @@ -18,18 +18,16 @@ import psutil -from chatsky.pipeline import ( - Pipeline, +from chatsky.core.service import ( ServiceGroup, ExtraHandlerRuntimeInfo, ServiceRuntimeInfo, to_service, ) -from chatsky.script import Context +from chatsky import Context, Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -184,6 +182,6 @@ def logging_service(ctx: Context, _, info: ServiceRuntimeInfo): pipeline = Pipeline(**pipeline_dict) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/pipeline/7_extra_handlers_and_extensions.py b/tutorials/pipeline/7_extra_handlers_and_extensions.py index a89ce332d..f9af88f0b 100644 --- a/tutorials/pipeline/7_extra_handlers_and_extensions.py +++ b/tutorials/pipeline/7_extra_handlers_and_extensions.py @@ -5,7 +5,7 @@ The following tutorial shows how pipeline can be extended by global extra handlers and custom functions. -Here, %mddoclink(api,pipeline.pipeline.pipeline,Pipeline.add_global_handler) +Here, %mddoclink(api,core.pipeline,Pipeline.add_global_handler) function is shown, that can be used to add extra handlers before and/or after all pipeline services. """ @@ -19,17 +19,16 @@ import random from datetime import datetime -from chatsky.pipeline import ( - Pipeline, +from chatsky.core.service import ( ComponentExecutionState, GlobalExtraHandlerType, ExtraHandlerRuntimeInfo, ServiceRuntimeInfo, ) +from chatsky import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) from chatsky.utils.testing.toy_script import HAPPY_PATH, TOY_SCRIPT @@ -134,6 +133,6 @@ async def long_service(_, __, info: ServiceRuntimeInfo): pipeline.add_global_handler(GlobalExtraHandlerType.AFTER_ALL, after_all) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/script/core/1_basics.py b/tutorials/script/core/1_basics.py index 49b39c41b..46f5b5ded 100644 --- a/tutorials/script/core/1_basics.py +++ b/tutorials/script/core/1_basics.py @@ -2,12 +2,9 @@ """ # Core: 1. Basics -This notebook shows basic tutorial of creating a simple dialog bot (agent). +This notebook shows a basic example of creating a simple dialog bot (agent). -Here, basic usege of %mddoclink(api,pipeline.pipeline.pipeline,Pipeline) -primitive is shown: its' creation with -%mddoclink(api,pipeline.pipeline.pipeline,Pipeline) -and execution. +Here, basic usage of %mddoclink(api,core.pipeline,Pipeline) is shown. Additionally, function %mddoclink(api,utils.testing.common,check_happy_path) that can be used for Pipeline testing is presented. @@ -18,14 +15,19 @@ # %pip install chatsky # %% -from chatsky.script import TRANSITIONS, RESPONSE, Message -from chatsky.pipeline import Pipeline -import chatsky.script.conditions as cnd +from chatsky import ( + TRANSITIONS, + RESPONSE, + Pipeline, + Transition as Tr, + conditions as cnd, + # all the aliases used in tutorials are available for direct import + # e.g. you can do `from chatsky import Tr` instead +) from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) @@ -33,9 +35,11 @@ """ First of all, to create a dialog agent, we need to create a dialog script. Below script means a dialog script. + A script is a dictionary, where the keys are the names of the flows. A script can contain multiple scripts, which is needed in order to divide a dialog into sub-dialogs and process them separately. + For example, the separation can be tied to the topic of the dialog. In this tutorial there is one flow called `greeting_flow`. @@ -44,10 +48,9 @@ * `RESPONSE` contains the response that the agent will return from the current node. -* `TRANSITIONS` describes transitions from the - current node to another nodes. This is a dictionary, - where keys are names of the nodes and - values are conditions of transition to them. +* `TRANSITIONS` is a list of %mddoclink(api,core.transition,Transition)s + that describes possible transitions from the current node as well as their + conditions and priorities. """ @@ -56,34 +59,37 @@ "greeting_flow": { "start_node": { # This is the initial node, # it doesn't contain a `RESPONSE`. - RESPONSE: Message(), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, - # If "Hi" == request of the user then we make the transition. + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], + # This transition means that the next node would be "node1" + # if user's message is "Hi" }, "node1": { - RESPONSE: Message( - text="Hi, how are you?" - ), # When the agent enters node1, + RESPONSE: "Hi, how are you?", + # When the bot enters node1, # return "Hi, how are you?". - TRANSITIONS: {"node2": cnd.exact_match("I'm fine, how are you?")}, + TRANSITIONS: [ + Tr(dst="node2", cnd=cnd.ExactMatch("I'm fine, how are you?")) + ], }, "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: {"node3": cnd.exact_match("Let's talk about music.")}, + RESPONSE: "Good. What do you want to talk about?", + TRANSITIONS: [ + Tr(dst="node3", cnd=cnd.ExactMatch("Let's talk about music.")) + ], }, "node3": { - RESPONSE: Message("Sorry, I can not talk about music now."), - TRANSITIONS: {"node4": cnd.exact_match("Ok, goodbye.")}, + RESPONSE: "Sorry, I can not talk about music now.", + TRANSITIONS: [Tr(dst="node4", cnd=cnd.ExactMatch("Ok, goodbye."))], }, "node4": { - RESPONSE: Message("Bye"), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, + RESPONSE: "Bye", + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], }, "fallback_node": { # We get to this node if the conditions # for switching to other nodes are not performed. - RESPONSE: Message("Ooops"), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, + RESPONSE: "Ooops", + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], }, } } @@ -128,13 +134,24 @@ # %% [markdown] """ A `Pipeline` is an object that processes user -inputs and returns responses. -To create the pipeline you need to pass the script (`toy_script`), +inputs and produces responses. + +To create the pipeline you need to pass the script (`script`), initial node (`start_label`) and the node to which the default transition will take place if none of the current conditions are met (`fallback_label`). -By default, if `fallback_label` is not set, -then its value becomes equal to `start_label`. + +If `fallback_label` is not set, it defaults to `start_label`. + +Roughly, the process is as follows: + +1. Pipeline receives a user request. +2. The next node is determined with the help of `TRANSITIONS`. +3. Response of the chosen node is sent to the user. + +For a more detailed description, see [here]( +%doclink(api,core.pipeline,Pipeline._run_pipeline) +). """ @@ -149,11 +166,12 @@ check_happy_path( pipeline, happy_path, + printout=True, ) # This is a function for automatic tutorial # running (testing tutorial) with `happy_path`. - # Run tutorial in interactive mode if not in IPython env - # and if `DISABLE_INTERACTIVE_MODE` is not set. if is_interactive_mode(): - run_interactive_mode(pipeline) - # This runs tutorial in interactive mode. + pipeline.run() + # this method runs the pipeline with the preconfigured interface + # which is CLI by default: it allows chatting with the bot + # via command line diff --git a/tutorials/script/core/2_conditions.py b/tutorials/script/core/2_conditions.py index b355f5908..3e27db673 100644 --- a/tutorials/script/core/2_conditions.py +++ b/tutorials/script/core/2_conditions.py @@ -5,10 +5,8 @@ This tutorial shows different options for setting transition conditions from one node to another. -Here, [conditions](%doclink(api,script.conditions.std_conditions)) +Here, [conditions](%doclink(api,conditions.standard)) for script transitions are shown. - -First of all, let's do all the necessary imports from Chatsky. """ # %pip install chatsky @@ -16,153 +14,152 @@ # %% import re -from chatsky.script import Context, TRANSITIONS, RESPONSE, Message -import chatsky.script.conditions as cnd -from chatsky.pipeline import Pipeline +from chatsky import ( + Context, + TRANSITIONS, + RESPONSE, + Message, + Pipeline, + BaseCondition, + Transition as Tr, + conditions as cnd, +) from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) # %% [markdown] """ -The transition condition is set by the function. -If this function returns the value `True`, -then the actor performs the corresponding transition. -Actor is responsible for processing user input and determining the appropriate -response based on the current state of the conversation and the script. -See tutorial 1 of pipeline (pipeline/1_basics) to learn more about Actor. +The transition condition is determined by +%mddoclink(api,core.script_function,BaseCondition). + +If this function returns `True`, +then the corresponding transition is considered possible. + Condition functions have signature - def func(ctx: Context, pipeline: Pipeline) -> bool + class MyCondition(BaseCondition): + async def call(self, ctx: Context) -> bool: -Out of the box `chatsky.script.conditions` offers the - following options for setting conditions: +This script covers the following pre-defined conditions: -* `exact_match` returns `True` if the user's request completely +- `ExactMatch` returns `True` if the user's request completely matches the value passed to the function. -* `regexp` returns `True` if the pattern matches the user's request, - while the user's request must be a string. - `regexp` has same signature as `re.compile` function. -* `aggregate` returns `bool` value as - a result after aggregate by `aggregate_func` - for input sequence of conditions. - `aggregate_func == any` by default. `aggregate` has alias `agg`. -* `any` returns `True` if one element of input sequence of conditions is `True`. - `any(input_sequence)` is equivalent to - `aggregate(input sequence, aggregate_func=any)`. -* `all` returns `True` if all elements of input +- `Regexp` returns `True` if the pattern matches the user's request. + `Regexp` has same signature as `re.compile` function. +- `Any` returns `True` if one element of input sequence of conditions is `True`. +- `All` returns `True` if All elements of input sequence of conditions are `True`. - `all(input_sequence)` is equivalent to - `aggregate(input sequence, aggregate_func=all)`. -* `negation` returns negation of passed function. `negation` has alias `neg`. -* `has_last_labels` covered in the following examples. -* `true` returns `True`. -* `false` returns `False`. - -For example function -``` -def always_true_condition(ctx: Context, pipeline: Pipeline) -> bool: - return True -``` -always returns `True` and `always_true_condition` function -is the same as `chatsky.script.conditions.std_conditions.true()`. - -The functions to be used in the `toy_script` are declared here. + +For a full list of available conditions see +[here](%doclink(api,conditions.standard)). + +The `cnd` field of `Transition` may also be a constant bool value. """ # %% -def hi_lower_case_condition(ctx: Context, _: Pipeline) -> bool: - request = ctx.last_request - # Returns True if `hi` in both uppercase and lowercase - # letters is contained in the user request. - if request is None or request.text is None: - return False - return "hi" in request.text.lower() +class HiLowerCase(BaseCondition): + """ + Return True if `hi` in both uppercase and lowercase + letters is contained in the user request. + """ + async def call(self, ctx: Context) -> bool: + request = ctx.last_request + return "hi" in request.text.lower() -def complex_user_answer_condition(ctx: Context, _: Pipeline) -> bool: - request = ctx.last_request - # The user request can be anything. - if request is None or request.misc is None: - return False - return {"some_key": "some_value"} == request.misc +# %% [markdown] +""" +Conditions are subclasses of `pydantic.BaseModel`. -def predetermined_condition(condition: bool): - # Wrapper for internal condition function. - def internal_condition_function(ctx: Context, _: Pipeline) -> bool: - # It always returns `condition`. - return condition +You can define custom fields to make them more customizable: +""" - return internal_condition_function + +# %% +class ComplexUserAnswer(BaseCondition): + """ + Checks if the misc field of the last message is of a certain value. + + Messages are more complex than just strings. + The misc field can be used to store metadata about the message. + More on that in the next tutorial. + """ + + value: dict + + async def call(self, ctx: Context) -> bool: + request = ctx.last_request + return request.misc == self.value + + +customized_condition = ComplexUserAnswer(value={"some_key": "some_value"}) # %% toy_script = { "greeting_flow": { - "start_node": { # This is the initial node, - # it doesn't contain a `RESPONSE`. - RESPONSE: Message(), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, + "start_node": { + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], # If "Hi" == request of user then we make the transition }, "node1": { - RESPONSE: Message("Hi, how are you?"), - TRANSITIONS: {"node2": cnd.regexp(r".*how are you", re.IGNORECASE)}, - # pattern matching (precompiled) + RESPONSE: "Hi, how are you?", + TRANSITIONS: [ + Tr( + dst="node2", + cnd=cnd.Regexp(r".*how are you", flags=re.IGNORECASE), + ) + ], + # pattern matching }, "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: { - "node3": cnd.all( - [cnd.regexp(r"talk"), cnd.regexp(r"about.*music")] + RESPONSE: "Good. What do you want to talk about?", + TRANSITIONS: [ + Tr( + dst="node3", + cnd=cnd.All( + cnd.Regexp(r"talk"), cnd.Regexp(r"about.*music") + ), ) - }, - # Mix sequence of conditions by `cnd.all`. - # `all` is alias `aggregate` with - # `aggregate_func` == `all`. + ], + # Combine sequences of conditions with `cnd.All` }, "node3": { - RESPONSE: Message("Sorry, I can not talk about music now."), - TRANSITIONS: {"node4": cnd.regexp(re.compile(r"Ok, goodbye."))}, - # pattern matching by precompiled pattern + RESPONSE: "Sorry, I can not talk about music now.", + TRANSITIONS: [ + Tr(dst="node4", cnd=cnd.Regexp(re.compile(r"Ok, goodbye."))) + ], }, "node4": { - RESPONSE: Message("bye"), - TRANSITIONS: { - "node1": cnd.any( - [ - hi_lower_case_condition, - cnd.exact_match("hello"), - ] + RESPONSE: "bye", + TRANSITIONS: [ + Tr( + dst="node1", + cnd=cnd.Any( + HiLowerCase(), + cnd.ExactMatch("hello"), + ), ) - }, - # Mix sequence of conditions by `cnd.any`. - # `any` is alias `aggregate` with - # `aggregate_func` == `any`. + ], + # Combine sequences of conditions with `cnd.Any` }, "fallback_node": { # We get to this node - # if an error occurred while the agent was running. - RESPONSE: Message("Ooops"), - TRANSITIONS: { - "node1": complex_user_answer_condition, - # The user request can be more than just a string. - # First we will check returned value of - # `complex_user_answer_condition`. - # If the value is `True` then we will go to `node1`. - # If the value is `False` then we will check a result of - # `predetermined_condition(True)` for `fallback_node`. - "fallback_node": predetermined_condition( - True - ), # or you can use `cnd.true()` - # Last condition function will return - # `true` and will repeat `fallback_node` - # if `complex_user_answer_condition` return `false`. - }, + # if no suitable transition was found + RESPONSE: "Ooops", + TRANSITIONS: [ + Tr(dst="node1", cnd=customized_condition), + # use a previously instantiated condition here + Tr(dst="start_node", cnd=False), + # This transition will never be made + Tr(dst="fallback_node"), + # `True` is the default value of `cnd` + # this transition will always be valid + ], }, } } @@ -219,6 +216,6 @@ def internal_condition_function(ctx: Context, _: Pipeline) -> bool: ) if __name__ == "__main__": - check_happy_path(pipeline, happy_path) + check_happy_path(pipeline, happy_path, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/script/core/3_responses.py b/tutorials/script/core/3_responses.py index fa05ced13..8c9dd0d7b 100644 --- a/tutorials/script/core/3_responses.py +++ b/tutorials/script/core/3_responses.py @@ -4,10 +4,8 @@ This tutorial shows different options for setting responses. -Here, [responses](%doclink(api,script.responses.std_responses)) +Here, [responses](%doclink(api,responses.standard)) that allow giving custom answers to users are shown. - -Let's do all the necessary imports from Chatsky. """ # %pip install chatsky @@ -15,104 +13,148 @@ # %% import re import random +from typing import Union + +from chatsky import ( + TRANSITIONS, + RESPONSE, + Context, + Message, + Pipeline, + Transition as Tr, + conditions as cnd, + responses as rsp, + destinations as dst, + BaseResponse, + MessageInitTypes, + AnyResponse, + AbsoluteNodeLabel, +) -from chatsky.script import TRANSITIONS, RESPONSE, Context, Message -import chatsky.script.responses as rsp -import chatsky.script.conditions as cnd - -from chatsky.pipeline import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) # %% [markdown] """ -The response can be set by Callable or *Message: - -* Callable objects. If the object is callable it must have a special signature: - - func(ctx: Context, pipeline: Pipeline) -> Message - -* *Message objects. If the object is *Message - it will be returned by the agent as a response. - - -The functions to be used in the `toy_script` are declared here. +Response of a node is determined by +%mddoclink(api,core.script_function,BaseResponse). + +Response can be constant in which case it is an instance +of %mddoclink(api,core.message,Message). + +`Message` has an option to be instantiated from a string +which is what we've been using so far. +Under the hood `RESPONSE: "text"` is converted into +`RESPONSE: Message(text="text")`. +This class should be used over simple strings when +some additional information needs to be sent such as images/metadata. + +More information on that can be found in the [media tutorial]( +%doclink(tutorial,script.responses.1_media) +). + +Instances of this class are returned by +%mddoclink(api,core.context,Context.last_request) and +%mddoclink(api,core.context,Context.last_response). +In the previous tutorial we showed how to access fields of messages +to build custom conditions. + +Node `RESPONSE` can also be set to a custom function. +This is demonstrated below: """ # %% -def cannot_talk_about_topic_response(ctx: Context, _: Pipeline) -> Message: - request = ctx.last_request - if request is None or request.text is None: - topic = None - else: - topic_pattern = re.compile(r"(.*talk about )(.*)\.") - topic = topic_pattern.findall(request.text) - topic = topic and topic[0] and topic[0][-1] - if topic: - return Message(f"Sorry, I can not talk about {topic} now.") - else: - return Message("Sorry, I can not talk about that now.") - - -def upper_case_response(response: Message): - # wrapper for internal response function - def func(_: Context, __: Pipeline) -> Message: +class CannotTalkAboutTopic(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + request = ctx.last_request + if request.text is None: + topic = None + else: + topic_pattern = re.compile(r"(.*talk about )(.*)\.") + topic = topic_pattern.findall(request.text) + topic = topic and topic[0] and topic[0][-1] + if topic: + return f"Sorry, I can not talk about {topic} now." + else: + return "Sorry, I can not talk about that now." + + +class UpperCase(BaseResponse): + response: AnyResponse # either const response or another BaseResponse + + def __init__(self, response: Union[MessageInitTypes, BaseResponse]): + # defining this allows passing response as a positional argument + # and allows to make a more detailed type annotation: + # AnyResponse cannot be a string but can be initialized from it, + # so MessageInitTypes annotates that we can init from a string + super().__init__(response=response) + + async def call(self, ctx: Context) -> MessageInitTypes: + response = await self.response(ctx) + # const response is converted to BaseResponse, + # so we call it regardless of the response type + if response.text is not None: response.text = response.text.upper() return response - return func +class FallbackTrace(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + return Message( + misc={ + "previous_node": await dst.Previous()(ctx), + "last_request": ctx.last_request, + } + ) + + +# %% [markdown] +""" +Chatsky provides one basic response as part of +the %mddoclink(api,responses.standard) module: -def fallback_trace_response(ctx: Context, _: Pipeline) -> Message: - return Message( - misc={ - "previous_node": list(ctx.labels.values())[-2], - "last_request": ctx.last_request, - } - ) +- `RandomChoice` randomly chooses a message out of the ones passed to it. +""" # %% toy_script = { "greeting_flow": { - "start_node": { # This is an initial node, - # it doesn't need a `RESPONSE`. - RESPONSE: Message(), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, - # If "Hi" == request of user then we make the transition + "start_node": { + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], }, "node1": { - RESPONSE: rsp.choice( - [ - Message("Hi, what is up?"), - Message("Hello, how are you?"), - ] + RESPONSE: rsp.RandomChoice( + "Hi, what is up?", + "Hello, how are you?", ), # Random choice from candidate list. - TRANSITIONS: {"node2": cnd.exact_match("I'm fine, how are you?")}, + TRANSITIONS: [ + Tr(dst="node2", cnd=cnd.ExactMatch("I'm fine, how are you?")) + ], }, "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: {"node3": cnd.exact_match("Let's talk about music.")}, + RESPONSE: "Good. What do you want to talk about?", + TRANSITIONS: [ + Tr(dst="node3", cnd=cnd.ExactMatch("Let's talk about music.")) + ], }, "node3": { - RESPONSE: cannot_talk_about_topic_response, - TRANSITIONS: {"node4": cnd.exact_match("Ok, goodbye.")}, + RESPONSE: CannotTalkAboutTopic(), + TRANSITIONS: [Tr(dst="node4", cnd=cnd.ExactMatch("Ok, goodbye."))], }, "node4": { - RESPONSE: upper_case_response(Message("bye")), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, + RESPONSE: UpperCase("bye"), + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], }, - "fallback_node": { # We get to this node - # if an error occurred while the agent was running. - RESPONSE: fallback_trace_response, - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, + "fallback_node": { + RESPONSE: FallbackTrace(), + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], }, } } @@ -134,38 +176,46 @@ def fallback_trace_response(ctx: Context, _: Pipeline) -> Message: ("Ok, goodbye.", "BYE"), # node3 -> node4 ("Hi", "Hello, how are you?"), # node4 -> node1 ( - Message("stop"), + "stop", Message( misc={ - "previous_node": ("greeting_flow", "node1"), + "previous_node": AbsoluteNodeLabel( + flow_name="greeting_flow", node_name="node1" + ), "last_request": Message("stop"), } ), ), # node1 -> fallback_node ( - Message("one"), + "one", Message( misc={ - "previous_node": ("greeting_flow", "fallback_node"), + "previous_node": AbsoluteNodeLabel( + flow_name="greeting_flow", node_name="fallback_node" + ), "last_request": Message("one"), } ), ), # f_n->f_n ( - Message("help"), + "help", Message( misc={ - "previous_node": ("greeting_flow", "fallback_node"), + "previous_node": AbsoluteNodeLabel( + flow_name="greeting_flow", node_name="fallback_node" + ), "last_request": Message("help"), } ), ), # f_n->f_n ( - Message("nope"), + "nope", Message( misc={ - "previous_node": ("greeting_flow", "fallback_node"), + "previous_node": AbsoluteNodeLabel( + flow_name="greeting_flow", node_name="fallback_node" + ), "last_request": Message("nope"), } ), @@ -196,6 +246,6 @@ def fallback_trace_response(ctx: Context, _: Pipeline) -> Message: ) if __name__ == "__main__": - check_happy_path(pipeline, happy_path) + check_happy_path(pipeline, happy_path, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/script/core/4_transitions.py b/tutorials/script/core/4_transitions.py index 0b7692498..482697c31 100644 --- a/tutorials/script/core/4_transitions.py +++ b/tutorials/script/core/4_transitions.py @@ -4,13 +4,11 @@ This tutorial shows settings for transitions between flows and nodes. -Here, [conditions](%doclink(api,script.conditions.std_conditions)) +Here, [conditions](%doclink(api,conditions.standard)) for transition between many different script steps are shown. Some of the destination steps can be set using -[labels](%doclink(api,script.labels.std_labels)). - -First of all, let's do all the necessary imports from Chatsky. +[destinations](%doclink(api,destinations.standard)). """ @@ -19,216 +17,249 @@ # %% import re -from chatsky.script import TRANSITIONS, RESPONSE, Context, ConstLabel, Message -import chatsky.script.conditions as cnd -import chatsky.script.labels as lbl -from chatsky.pipeline import Pipeline +from chatsky import ( + TRANSITIONS, + RESPONSE, + Context, + NodeLabelInitTypes, + Pipeline, + Transition as Tr, + BaseDestination, + conditions as cnd, + destinations as dst, +) from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) # %% [markdown] """ -Let's define the functions with a special type of return value: +The `TRANSITIONS` keyword is used to determine a list of transitions from +the current node. After receiving user request, Pipeline will choose the +next node relying on that list. +If no transition in the list is suitable, transition will be made +to the fallback node. + +Each transition is represented by the %mddoclink(api,core.transition,Transition) +class. + +It has three main fields: + +- dst: Destination determines the node to which the transition is made. +- cnd: Condition determines if the transition is allowed. +- priority: Allows choosing one of the transitions if several are allowed. + Higher priority transitions will be chosen over the rest. + If priority is not set, + %mddoclink(api,core.pipeline,Pipeline.default_priority) + is used instead. + Default priority is 1 by default (but may be set via Pipeline). + +For more details on how the next node is chosen see +[here](%doclink(api,core.transition,get_next_label)). + +Like conditions, all of these fields can be either constant values or +custom functions (%mddoclink(api,core.script_function,BaseDestination), +%mddoclink(api,core.script_function,BaseCondition), +%mddoclink(api,core.script_function,BasePriority)). +""" - ConstLabel == Flow Name; Node Name; Priority +# %% [markdown] +""" +## Destinations -These functions return Labels that -determine destination and priority of a specific transition. +Destination node is specified with a %mddoclink(api,core.node_label,NodeLabel) +class. -Labels consist of: +It contains two field: -1. Flow name of the destination node - (optional; defaults to flow name of the current node). -2. Node name of the destination node - (required). -3. Priority of the transition (more on that later) - (optional; defaults to pipeline's - [label_priority](%doclink(api,pipeline.pipeline.pipeline))). +- "flow_name": Name of the flow the node belongs to. + Optional; if not set, will use the flow of the current node. +- "node_name": Name of the node inside the flow. -An example of omitting optional arguments is shown in the body of the -`greeting_flow_n2_transition` function: +Instances of this class can be initialized from a tuple of two strings +(flow name and node name) or a single string (node name; relative flow name). +This happens automatically for return values of `BaseDestination` +and for the `dst` field of `Transition`. """ # %% -def greeting_flow_n2_transition(_: Context, __: Pipeline) -> ConstLabel: - return "greeting_flow", "node2" - - -def high_priority_node_transition(flow_name, node_name): - def transition(_: Context, __: Pipeline) -> ConstLabel: - return flow_name, node_name, 2.0 - - return transition +class GreetingFlowNode2(BaseDestination): + async def call(self, ctx: Context) -> NodeLabelInitTypes: + return "greeting_flow", "node2" # %% [markdown] """ -Priority is needed to select a condition -in the situation where more than one condition is `True`. -All conditions in `TRANSITIONS` are being checked. -Of the set of `True` conditions, -the one that has the highest priority will be executed. -Of the set of `True` conditions with largest -priority the first met condition will be executed. - -Out of the box `chatsky.script.core.labels` -offers the following methods: - -* `lbl.repeat()` returns transition handler - which returns `ConstLabel` to the last node, - -* `lbl.previous()` returns transition handler - which returns `ConstLabel` to the previous node, - -* `lbl.to_start()` returns transition handler - which returns `ConstLabel` to the start node, - -* `lbl.to_fallback()` returns transition - handler which returns `ConstLabel` to the fallback node, - -* `lbl.forward()` returns transition handler - which returns `ConstLabel` to the forward node, - -* `lbl.backward()` returns transition handler - which returns `ConstLabel` to the backward node. - -There are three flows here: `global_flow`, `greeting_flow`, `music_flow`. +Chatsky provides several basic transitions as part of +the %mddoclink(api,destinations.standard) module: + +- `FromHistory` returns a node from label history. + `Current` and `Previous` are subclasses of it that return specific nodes + (current node and previous node respectively). +- `Start` returns the start node. +- `Fallback` returns the fallback node. +- `Forward` returns the next node (in order of definition) + in the current flow relative to the current node. +- `Backward` returns the previous node (in order of definition) + in the current flow relative to the current node. """ # %% toy_script = { "global_flow": { - "start_node": { # This is an initial node, - # it doesn't need a `RESPONSE`. - RESPONSE: Message(), - TRANSITIONS: { - ("music_flow", "node1"): cnd.regexp( - r"talk about music" - ), # first check - ("greeting_flow", "node1"): cnd.regexp( - r"hi|hello", re.IGNORECASE - ), # second check - "fallback_node": cnd.true(), # third check - # "fallback_node" is equivalent to - # ("global_flow", "fallback_node"). - }, + "start_node": { + TRANSITIONS: [ + Tr( + dst=("music_flow", "node1"), + cnd=cnd.Regexp(r"talk about music"), + # this condition is checked first. + # if it fails, pipeline will try the next transition + ), + Tr( + dst=("greeting_flow", "node1"), + cnd=cnd.Regexp(r"hi|hello", flags=re.IGNORECASE), + ), + Tr( + dst="fallback_node", + # a single string references a node in the same flow + ), + # this transition will only be made if previous ones fail + ] }, - "fallback_node": { # We get to this node if - # an error occurred while the agent was running. - RESPONSE: Message("Ooops"), - TRANSITIONS: { - ("music_flow", "node1"): cnd.regexp( - r"talk about music" - ), # first check - ("greeting_flow", "node1"): cnd.regexp( - r"hi|hello", re.IGNORECASE - ), # second check - lbl.previous(): cnd.regexp( - r"previous", re.IGNORECASE - ), # third check - # lbl.previous() is equivalent - # to ("previous_flow", "previous_node") - lbl.repeat(): cnd.true(), # fourth check - # lbl.repeat() is equivalent to ("global_flow", "fallback_node") - }, + "fallback_node": { + RESPONSE: "Ooops", + TRANSITIONS: [ + Tr( + dst=("music_flow", "node1"), + cnd=cnd.Regexp(r"talk about music"), + ), + Tr( + dst=("greeting_flow", "node1"), + cnd=cnd.Regexp(r"hi|hello", flags=re.IGNORECASE), + ), + Tr( + dst=dst.Previous(), + cnd=cnd.Regexp(r"previous", flags=re.IGNORECASE), + ), + Tr( + dst=dst.Current(), # this goes to the current node + # i.e. fallback node + ), + ], }, }, "greeting_flow": { "node1": { - RESPONSE: Message("Hi, how are you?"), - # When the agent goes to node1, we return "Hi, how are you?" - TRANSITIONS: { - ( - "global_flow", - "fallback_node", - 0.1, - ): cnd.true(), # second check - "node2": cnd.regexp(r"how are you"), # first check - # "node2" is equivalent to ("greeting_flow", "node2", 1.0) - }, + RESPONSE: "Hi, how are you?", + TRANSITIONS: [ + Tr( + dst=("global_flow", "fallback_node"), + priority=0.1, + ), # due to low priority (default priority is 1) + # this transition will be made if the next one fails + Tr(dst="node2", cnd=cnd.Regexp(r"how are you")), + ], }, "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: { - lbl.to_fallback(0.1): cnd.true(), # fourth check - # lbl.to_fallback(0.1) is equivalent - # to ("global_flow", "fallback_node", 0.1) - lbl.forward(0.5): cnd.regexp(r"talk about"), # third check - # lbl.forward(0.5) is equivalent - # to ("greeting_flow", "node3", 0.5) - ("music_flow", "node1"): cnd.regexp( - r"talk about music" - ), # first check - # ("music_flow", "node1") is equivalent - # to ("music_flow", "node1", 1.0) - lbl.previous(): cnd.regexp( - r"previous", re.IGNORECASE - ), # second check - }, + RESPONSE: "Good. What do you want to talk about?", + TRANSITIONS: [ + Tr( + dst=dst.Fallback(), + priority=0.1, + ), + # there is no need to specify such transition: + # For any node if all transitions fail, + # fallback node becomes the next node. + # Here, this transition exists for demonstration purposes. + Tr( + dst=dst.Forward(), # i.e. "node3" of this flow + cnd=cnd.Regexp(r"talk about"), + priority=0.5, + ), # this transition is the third candidate + Tr( + dst=("music_flow", "node1"), + cnd=cnd.Regexp(r"talk about music"), + ), # this transition is the first candidate + Tr( + dst=dst.Previous(), + cnd=cnd.Regexp(r"previous", flags=re.IGNORECASE), + ), # this transition is the second candidate + ], }, "node3": { - RESPONSE: Message("Sorry, I can not talk about that now."), - TRANSITIONS: {lbl.forward(): cnd.regexp(r"bye")}, + RESPONSE: "Sorry, I can not talk about that now.", + TRANSITIONS: [Tr(dst=dst.Forward(), cnd=cnd.Regexp(r"bye"))], }, "node4": { - RESPONSE: Message("Bye"), - TRANSITIONS: { - "node1": cnd.regexp(r"hi|hello", re.IGNORECASE), # first check - lbl.to_fallback(): cnd.true(), # second check - }, + RESPONSE: "Bye", + TRANSITIONS: [ + Tr( + dst="node1", + cnd=cnd.Regexp(r"hi|hello", flags=re.IGNORECASE), + ) + ], }, }, "music_flow": { "node1": { - RESPONSE: Message( - text="I love `System of a Down` group, " - "would you like to talk about it?" - ), - TRANSITIONS: { - lbl.forward(): cnd.regexp(r"yes|yep|ok", re.IGNORECASE), - lbl.to_fallback(): cnd.true(), - }, + RESPONSE: "I love `System of a Down` group, " + "would you like to talk about it?", + TRANSITIONS: [ + Tr( + dst=dst.Forward(), + cnd=cnd.Regexp(r"yes|yep|ok", flags=re.IGNORECASE), + ) + ], }, "node2": { - RESPONSE: Message( - text="System of a Down is " - "an Armenian-American heavy metal band formed in 1994." - ), - TRANSITIONS: { - lbl.forward(): cnd.regexp(r"next", re.IGNORECASE), - lbl.repeat(): cnd.regexp(r"repeat", re.IGNORECASE), - lbl.to_fallback(): cnd.true(), - }, + RESPONSE: "System of a Down is an Armenian-American " + "heavy metal band formed in 1994.", + TRANSITIONS: [ + Tr( + dst=dst.Forward(), + cnd=cnd.Regexp(r"next", flags=re.IGNORECASE), + ), + Tr( + dst=dst.Current(), + cnd=cnd.Regexp(r"repeat", flags=re.IGNORECASE), + ), + ], }, "node3": { - RESPONSE: Message( - text="The band achieved commercial success " - "with the release of five studio albums." - ), - TRANSITIONS: { - lbl.forward(): cnd.regexp(r"next", re.IGNORECASE), - lbl.backward(): cnd.regexp(r"back", re.IGNORECASE), - lbl.repeat(): cnd.regexp(r"repeat", re.IGNORECASE), - lbl.to_fallback(): cnd.true(), - }, + RESPONSE: "The band achieved commercial success " + "with the release of five studio albums.", + TRANSITIONS: [ + Tr( + dst=dst.Forward(), + cnd=cnd.Regexp(r"next", flags=re.IGNORECASE), + ), + Tr( + dst=dst.Backward(), + cnd=cnd.Regexp(r"back", flags=re.IGNORECASE), + ), + Tr( + dst=dst.Current(), + cnd=cnd.Regexp(r"repeat", flags=re.IGNORECASE), + ), + ], }, "node4": { - RESPONSE: Message("That's all what I know."), - TRANSITIONS: { - greeting_flow_n2_transition: cnd.regexp( - r"next", re.IGNORECASE - ), # second check - high_priority_node_transition( - "greeting_flow", "node4" - ): cnd.regexp( - r"next time", re.IGNORECASE - ), # first check - lbl.to_fallback(): cnd.true(), # third check - }, + RESPONSE: "That's all I know.", + TRANSITIONS: [ + Tr( + dst=GreetingFlowNode2(), + cnd=cnd.Regexp(r"next", flags=re.IGNORECASE), + ), + Tr( + dst=("greeting_flow", "node4"), + cnd=cnd.Regexp(r"next time", flags=re.IGNORECASE), + priority=2, + ), # "next" is contained in "next_time" so we need higher + # priority here. + # Otherwise, this transition will never be made + ], }, }, } @@ -269,12 +300,12 @@ def transition(_: Context, __: Pipeline) -> ConstLabel: "The band achieved commercial success " "with the release of five studio albums.", ), - ("next", "That's all what I know."), + ("next", "That's all I know."), ( "next", "Good. What do you want to talk about?", ), - ("previous", "That's all what I know."), + ("previous", "That's all I know."), ("next time", "Bye"), ("stop", "Ooops"), ("previous", "Bye"), @@ -302,6 +333,6 @@ def transition(_: Context, __: Pipeline) -> ConstLabel: ) if __name__ == "__main__": - check_happy_path(pipeline, happy_path) + check_happy_path(pipeline, happy_path, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/script/core/5_global_local.py b/tutorials/script/core/5_global_local.py new file mode 100644 index 000000000..7010a6f16 --- /dev/null +++ b/tutorials/script/core/5_global_local.py @@ -0,0 +1,254 @@ +# %% [markdown] +""" +# Core: 5. Global and Local nodes +""" + + +# %pip install chatsky + +# %% +import re + +from chatsky import ( + GLOBAL, + TRANSITIONS, + RESPONSE, + Pipeline, + Transition as Tr, + conditions as cnd, + destinations as dst, +) +from chatsky.utils.testing.common import ( + check_happy_path, + is_interactive_mode, +) + +# %% [markdown] +""" +Keywords `GLOBAL` and `LOCAL` are used to define global and local nodes +respectively. Global node is defined at the script level (along with flows) +and local node is defined at the flow level (along with nodes inside a flow). + +Every local node inherits properties from the global node. +Every node inherits properties from the local node (of its flow). + +For example, if we are to set list `A` as transitions for the +local node of a flow, then every node of that flow would effectively +have the `A` list extended with its own transitions. + +
+ +To sum up transition priorities: + +Transition A is of higher priority compared to Transition B: + +1. If A.priority > B.priority; OR +2. If A is a node transition and B is a local or global transition; + or A is a local transition and B is a global transition; OR +3. If A is defined in the transition list earlier than B. + +
+ +For more information on node inheritance, see [here]( +%doclink(api,core.script,Script.get_inherited_node) +). + +
+ +Note + +Property %mddoclink(api,core.context,Context.current_node) does not return +the current node as is. Instead it returns a node that is modified +by the global and local nodes. + +
+""" + +# %% +toy_script = { + GLOBAL: { + TRANSITIONS: [ + Tr( + dst=("greeting_flow", "node1"), + cnd=cnd.Regexp(r"\b(hi|hello)\b", flags=re.I), + priority=1.1, + ), + Tr( + dst=("music_flow", "node1"), + cnd=cnd.Regexp(r"talk about music"), + priority=1.1, + ), + Tr( + dst=dst.Forward(), + cnd=cnd.All( + cnd.Regexp(r"next\b"), + cnd.CheckLastLabels( + labels=[("music_flow", i) for i in ["node2", "node3"]] + ), # this checks if the current node is + # music_flow.node2 or music_flow.node3 + ), + ), + Tr( + dst=dst.Current(), + cnd=cnd.All( + cnd.Regexp(r"repeat", flags=re.I), + cnd.Negation( + cnd.CheckLastLabels(flow_labels=["global_flow"]) + ), + ), + priority=0.2, + ), + ], + }, + "global_flow": { + "start_node": {}, + "fallback_node": { + RESPONSE: "Ooops", + TRANSITIONS: [ + Tr( + dst=dst.Previous(), + cnd=cnd.Regexp(r"previous", flags=re.I), + ) + ], + }, + }, + "greeting_flow": { + "node1": { + RESPONSE: "Hi, how are you?", + TRANSITIONS: [Tr(dst="node2", cnd=cnd.Regexp(r"how are you"))], + }, + "node2": { + RESPONSE: "Good. What do you want to talk about?", + TRANSITIONS: [ + Tr( + dst=dst.Forward(), + cnd=cnd.Regexp(r"talk about"), + priority=0.5, + ), + Tr( + dst=dst.Previous(), + cnd=cnd.Regexp(r"previous", flags=re.I), + ), + ], + }, + "node3": { + RESPONSE: "Sorry, I can not talk about that now.", + TRANSITIONS: [Tr(dst=dst.Forward(), cnd=cnd.Regexp(r"bye"))], + }, + "node4": {RESPONSE: "bye"}, + # This node does not define its own transitions. + # It will use global transitions only. + }, + "music_flow": { + "node1": { + RESPONSE: "I love `System of a Down` group, " + "would you like to talk about it?", + TRANSITIONS: [ + Tr( + dst=dst.Forward(), + cnd=cnd.Regexp(r"yes|yep|ok", flags=re.IGNORECASE), + ) + ], + }, + "node2": { + RESPONSE: "System of a Down is an Armenian-American " + "heavy metal band formed in 1994.", + }, + "node3": { + RESPONSE: "The band achieved commercial success " + "with the release of five studio albums.", + TRANSITIONS: [ + Tr( + dst=dst.Backward(), + cnd=cnd.Regexp(r"back", flags=re.IGNORECASE), + ), + ], + }, + "node4": { + RESPONSE: "That's all I know.", + TRANSITIONS: [ + Tr( + dst=("greeting_flow", "node4"), + cnd=cnd.Regexp(r"next time", flags=re.I), + ), + Tr( + dst=("greeting_flow", "node2"), + cnd=cnd.Regexp(r"next", flags=re.I), + ), + ], + }, + }, +} + +# testing +happy_path = ( + ("hi", "Hi, how are you?"), + ( + "i'm fine, how are you?", + "Good. What do you want to talk about?", + ), + ( + "talk about music.", + "I love `System of a Down` group, " "would you like to talk about it?", + ), + ( + "yes", + "System of a Down is " + "an Armenian-American heavy metal band formed in 1994.", + ), + ( + "next", + "The band achieved commercial success " + "with the release of five studio albums.", + ), + ( + "back", + "System of a Down is " + "an Armenian-American heavy metal band formed in 1994.", + ), + ( + "repeat", + "System of a Down is " + "an Armenian-American heavy metal band formed in 1994.", + ), + ( + "next", + "The band achieved commercial success " + "with the release of five studio albums.", + ), + ("next", "That's all I know."), + ( + "next", + "Good. What do you want to talk about?", + ), + ("previous", "That's all I know."), + ("next time", "bye"), + ("stop", "Ooops"), + ("previous", "bye"), + ("stop", "Ooops"), + ("nope", "Ooops"), + ("hi", "Hi, how are you?"), + ("stop", "Ooops"), + ("previous", "Hi, how are you?"), + ( + "i'm fine, how are you?", + "Good. What do you want to talk about?", + ), + ( + "let's talk about something.", + "Sorry, I can not talk about that now.", + ), + ("Ok, goodbye.", "bye"), +) + +# %% +pipeline = Pipeline( + script=toy_script, + start_label=("global_flow", "start_node"), + fallback_label=("global_flow", "fallback_node"), +) + +if __name__ == "__main__": + check_happy_path(pipeline, happy_path, printout=True) + if is_interactive_mode(): + pipeline.run() diff --git a/tutorials/script/core/5_global_transitions.py b/tutorials/script/core/5_global_transitions.py deleted file mode 100644 index 2b3c45005..000000000 --- a/tutorials/script/core/5_global_transitions.py +++ /dev/null @@ -1,208 +0,0 @@ -# %% [markdown] -""" -# Core: 5. Global transitions - -This tutorial shows the global setting of transitions. - -Here, global [conditions](%doclink(api,script.conditions.std_conditions)) -for default transition between many different script steps are shown. - -First of all, let's do all the necessary imports from Chatsky. -""" - - -# %pip install chatsky - -# %% -import re - -from chatsky.script import GLOBAL, TRANSITIONS, RESPONSE, Message -import chatsky.script.conditions as cnd -import chatsky.script.labels as lbl -from chatsky.pipeline import Pipeline -from chatsky.utils.testing.common import ( - check_happy_path, - is_interactive_mode, - run_interactive_mode, -) - -# %% [markdown] -""" -The keyword `GLOBAL` is used to define a global node. -There can be only one global node in a script. -The value that corresponds to this key has the -`dict` type with the same keywords as regular nodes. -The global node is defined above the flow level as opposed to regular nodes. -This node allows to define default global values for all nodes. - -There are `GLOBAL` node and three flows: -`global_flow`, `greeting_flow`, `music_flow`. -""" - -# %% -toy_script = { - GLOBAL: { - TRANSITIONS: { - ("greeting_flow", "node1", 1.1): cnd.regexp( - r"\b(hi|hello)\b", re.I - ), # first check - ("music_flow", "node1", 1.1): cnd.regexp( - r"talk about music" - ), # second check - lbl.to_fallback(0.1): cnd.true(), # fifth check - lbl.forward(): cnd.all( - [ - cnd.regexp(r"next\b"), - cnd.has_last_labels( - labels=[("music_flow", i) for i in ["node2", "node3"]] - ), - ] # third check - ), - lbl.repeat(0.2): cnd.all( - [ - cnd.regexp(r"repeat", re.I), - cnd.negation( - cnd.has_last_labels(flow_labels=["global_flow"]) - ), - ] # fourth check - ), - } - }, - "global_flow": { - "start_node": { - RESPONSE: Message() - }, # This is an initial node, it doesn't need a `RESPONSE`. - "fallback_node": { # We get to this node - # if an error occurred while the agent was running. - RESPONSE: Message("Ooops"), - TRANSITIONS: {lbl.previous(): cnd.regexp(r"previous", re.I)}, - # lbl.previous() is equivalent to - # ("previous_flow", "previous_node", 1.0) - }, - }, - "greeting_flow": { - "node1": { - RESPONSE: Message("Hi, how are you?"), - TRANSITIONS: {"node2": cnd.regexp(r"how are you")}, - # "node2" is equivalent to ("greeting_flow", "node2", 1.0) - }, - "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: { - lbl.forward(0.5): cnd.regexp(r"talk about"), - # lbl.forward(0.5) is equivalent to - # ("greeting_flow", "node3", 0.5) - lbl.previous(): cnd.regexp(r"previous", re.I), - }, - }, - "node3": { - RESPONSE: Message("Sorry, I can not talk about that now."), - TRANSITIONS: {lbl.forward(): cnd.regexp(r"bye")}, - }, - "node4": {RESPONSE: Message("bye")}, - # Only the global transitions setting are used in this node. - }, - "music_flow": { - "node1": { - RESPONSE: Message( - text="I love `System of a Down` group, " - "would you like to talk about it?" - ), - TRANSITIONS: {lbl.forward(): cnd.regexp(r"yes|yep|ok", re.I)}, - }, - "node2": { - RESPONSE: Message( - text="System of a Down is " - "an Armenian-American heavy metal band formed in 1994." - ) - # Only the global transitions setting are used in this node. - }, - "node3": { - RESPONSE: Message( - text="The band achieved commercial success " - "with the release of five studio albums." - ), - TRANSITIONS: {lbl.backward(): cnd.regexp(r"back", re.I)}, - }, - "node4": { - RESPONSE: Message("That's all what I know."), - TRANSITIONS: { - ("greeting_flow", "node4"): cnd.regexp(r"next time", re.I), - ("greeting_flow", "node2"): cnd.regexp(r"next", re.I), - }, - }, - }, -} - -# testing -happy_path = ( - ("hi", "Hi, how are you?"), - ( - "i'm fine, how are you?", - "Good. What do you want to talk about?", - ), - ( - "talk about music.", - "I love `System of a Down` group, " "would you like to talk about it?", - ), - ( - "yes", - "System of a Down is " - "an Armenian-American heavy metal band formed in 1994.", - ), - ( - "next", - "The band achieved commercial success " - "with the release of five studio albums.", - ), - ( - "back", - "System of a Down is " - "an Armenian-American heavy metal band formed in 1994.", - ), - ( - "repeat", - "System of a Down is " - "an Armenian-American heavy metal band formed in 1994.", - ), - ( - "next", - "The band achieved commercial success " - "with the release of five studio albums.", - ), - ("next", "That's all what I know."), - ( - "next", - "Good. What do you want to talk about?", - ), - ("previous", "That's all what I know."), - ("next time", "bye"), - ("stop", "Ooops"), - ("previous", "bye"), - ("stop", "Ooops"), - ("nope", "Ooops"), - ("hi", "Hi, how are you?"), - ("stop", "Ooops"), - ("previous", "Hi, how are you?"), - ( - "i'm fine, how are you?", - "Good. What do you want to talk about?", - ), - ( - "let's talk about something.", - "Sorry, I can not talk about that now.", - ), - ("Ok, goodbye.", "bye"), -) - -# %% -pipeline = Pipeline( - script=toy_script, - start_label=("global_flow", "start_node"), - fallback_label=("global_flow", "fallback_node"), -) - -if __name__ == "__main__": - check_happy_path(pipeline, happy_path) - if is_interactive_mode(): - run_interactive_mode(pipeline) diff --git a/tutorials/script/core/6_context_serialization.py b/tutorials/script/core/6_context_serialization.py index fbbcc9bb1..b74e1abe3 100644 --- a/tutorials/script/core/6_context_serialization.py +++ b/tutorials/script/core/6_context_serialization.py @@ -1,9 +1,6 @@ # %% [markdown] """ # Core: 6. Context serialization - -This tutorial shows context serialization. -First of all, let's do all the necessary imports from Chatsky. """ # %pip install chatsky @@ -11,35 +8,34 @@ # %% import logging -from chatsky.script import TRANSITIONS, RESPONSE, Context, Message -import chatsky.script.conditions as cnd +from chatsky import ( + TRANSITIONS, + RESPONSE, + Context, + Pipeline, + Transition as Tr, + BaseResponse, + MessageInitTypes, +) -from chatsky.pipeline import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) -# %% [markdown] -""" -This function returns the user request number. -""" - - # %% -def response_handler(ctx: Context, _: Pipeline) -> Message: - return Message(f"answer {len(ctx.requests)}") +class RequestCounter(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + return f"answer {len(ctx.requests)}" # %% -# a dialog script toy_script = { "flow_start": { "node_start": { - RESPONSE: response_handler, - TRANSITIONS: {("flow_start", "node_start"): cnd.true()}, + RESPONSE: RequestCounter(), + TRANSITIONS: [Tr(dst=("flow_start", "node_start"))], } } } @@ -84,6 +80,6 @@ def process_response(ctx: Context): ) if __name__ == "__main__": - check_happy_path(pipeline, happy_path) + check_happy_path(pipeline, happy_path, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/script/core/7_pre_response_processing.py b/tutorials/script/core/7_pre_response_processing.py index 0fe561d1e..cc7cf9b14 100644 --- a/tutorials/script/core/7_pre_response_processing.py +++ b/tutorials/script/core/7_pre_response_processing.py @@ -2,56 +2,102 @@ """ # Core: 7. Pre-response processing -This tutorial shows pre-response processing feature. - -Here, %mddoclink(api,script.core.keywords,Keywords.PRE_RESPONSE_PROCESSING) +Here, %mddoclink(api,core.script,PRE_RESPONSE) is demonstrated which can be used for additional context processing before response handlers. - -There are also some other %mddoclink(api,script.core.keywords,Keywords) -worth attention used in this tutorial. - -First of all, let's do all the necessary imports from Chatsky. """ # %pip install chatsky # %% -from chatsky.script import ( +from chatsky import ( GLOBAL, LOCAL, RESPONSE, TRANSITIONS, - PRE_RESPONSE_PROCESSING, + PRE_RESPONSE, Context, Message, + MessageInitTypes, + BaseResponse, + Transition as Tr, + Pipeline, + destinations as dst, + processing as proc, ) -import chatsky.script.labels as lbl -import chatsky.script.conditions as cnd -from chatsky.pipeline import Pipeline from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) +# %% [markdown] +""" +Processing functions have the same signature as +conditions, responses or destinations +except they don't return anything: + +.. python: + + class MyProcessing(BaseProcessing): + async def call(self, ctx: Context) -> None: + ... + + +The main way for processing functions to interact with the script +is modifying `ctx.current_node`, which is used by pipeline +to store a copy of the current node in script. +Any of its attributes can be safely edited, and these changes will +only have an effect during the current turn of the current context. +""" + + +# %% [markdown] +""" +In this tutorial we'll subclass +%mddoclink(api,processing.standard,ModifyResponse) +processing function so that it would modify response +of the current node to include a prefix. +""" + + # %% -def add_prefix(prefix): - def add_prefix_processing(ctx: Context, _: Pipeline): - processed_node = ctx.current_node - processed_node.response = Message( - text=f"{prefix}: {processed_node.response.text}" - ) +class AddPrefix(proc.ModifyResponse): + prefix: str + + def __init__(self, prefix: str): + # basemodel does not allow positional arguments by default + super().__init__(prefix=prefix) - return add_prefix_processing + async def modified_response( + self, original_response: BaseResponse, ctx: Context + ) -> MessageInitTypes: + result = await original_response(ctx) + + if result.text is not None: + result.text = f"{self.prefix}: {result.text}" + return result # %% [markdown] """ -`PRE_RESPONSE_PROCESSING` is a keyword that -can be used in `GLOBAL`, `LOCAL` or nodes. +
+ +Tip + +You can use `ModifyResponse` to catch exceptions in response functions: + +.. python: + + class ExceptionHandler(proc.ModifyResponse): + async def modified_response(self, original_response, ctx): + try: + return await original_response(ctx) + except Exception as exc: + return str(exc) + +
""" @@ -59,47 +105,42 @@ def add_prefix_processing(ctx: Context, _: Pipeline): toy_script = { "root": { "start": { - RESPONSE: Message(), - TRANSITIONS: {("flow", "step_0"): cnd.true()}, + TRANSITIONS: [Tr(dst=("flow", "step_0"))], }, - "fallback": {RESPONSE: Message("the end")}, + "fallback": {RESPONSE: "the end"}, }, GLOBAL: { - PRE_RESPONSE_PROCESSING: { - "proc_name_1": add_prefix("l1_global"), - "proc_name_2": add_prefix("l2_global"), + PRE_RESPONSE: { + "proc_name_1": AddPrefix("l1_global"), + "proc_name_2": AddPrefix("l2_global"), } }, "flow": { LOCAL: { - PRE_RESPONSE_PROCESSING: { - "proc_name_2": add_prefix("l2_local"), - "proc_name_3": add_prefix("l3_local"), - } + PRE_RESPONSE: { + "proc_name_2": AddPrefix("l2_local"), + "proc_name_3": AddPrefix("l3_local"), + }, + TRANSITIONS: [Tr(dst=dst.Forward(loop=True))], }, "step_0": { - RESPONSE: Message("first"), - TRANSITIONS: {lbl.forward(): cnd.true()}, + RESPONSE: "first", }, "step_1": { - PRE_RESPONSE_PROCESSING: {"proc_name_1": add_prefix("l1_step_1")}, - RESPONSE: Message("second"), - TRANSITIONS: {lbl.forward(): cnd.true()}, + PRE_RESPONSE: {"proc_name_1": AddPrefix("l1_step_1")}, + RESPONSE: "second", }, "step_2": { - PRE_RESPONSE_PROCESSING: {"proc_name_2": add_prefix("l2_step_2")}, - RESPONSE: Message("third"), - TRANSITIONS: {lbl.forward(): cnd.true()}, + PRE_RESPONSE: {"proc_name_2": AddPrefix("l2_step_2")}, + RESPONSE: "third", }, "step_3": { - PRE_RESPONSE_PROCESSING: {"proc_name_3": add_prefix("l3_step_3")}, - RESPONSE: Message("fourth"), - TRANSITIONS: {lbl.forward(): cnd.true()}, + PRE_RESPONSE: {"proc_name_3": AddPrefix("l3_step_3")}, + RESPONSE: "fourth", }, "step_4": { - PRE_RESPONSE_PROCESSING: {"proc_name_4": add_prefix("l4_step_4")}, - RESPONSE: Message("fifth"), - TRANSITIONS: {"step_0": cnd.true()}, + PRE_RESPONSE: {"proc_name_4": AddPrefix("l4_step_4")}, + RESPONSE: "fifth", }, }, } @@ -124,6 +165,6 @@ def add_prefix_processing(ctx: Context, _: Pipeline): ) if __name__ == "__main__": - check_happy_path(pipeline, happy_path) + check_happy_path(pipeline, happy_path, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/script/core/8_misc.py b/tutorials/script/core/8_misc.py index 555f8eebc..a2dcf75c7 100644 --- a/tutorials/script/core/8_misc.py +++ b/tutorials/script/core/8_misc.py @@ -4,16 +4,14 @@ This tutorial shows `MISC` (miscellaneous) keyword usage. -See %mddoclink(api,script.core.keywords,Keywords.MISC) +See %mddoclink(api,core.script,MISC) for more information. - -First of all, let's do all the necessary imports from Chatsky. """ # %pip install chatsky # %% -from chatsky.script import ( +from chatsky import ( GLOBAL, LOCAL, RESPONSE, @@ -21,35 +19,42 @@ MISC, Context, Message, + Pipeline, + MessageInitTypes, + BaseResponse, + Transition as Tr, + destinations as dst, ) -import chatsky.script.labels as lbl -import chatsky.script.conditions as cnd -from chatsky.pipeline import Pipeline + from chatsky.utils.testing.common import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) +# %% [markdown] +""" +`MISC` is used to store custom node data. +It can be accessed via `ctx.current_node.misc`. +""" + + # %% -def custom_response(ctx: Context, _: Pipeline) -> Message: - current_node = ctx.current_node - current_misc = current_node.misc if current_node is not None else None - return Message( - text=f"ctx.last_label={ctx.last_label}: " - f"current_node.misc={current_misc}" - ) +class CustomResponse(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + return ( + f"node_name={ctx.last_label.node_name}: " + f"current_node.misc={ctx.current_node.misc}" + ) # %% toy_script = { "root": { "start": { - RESPONSE: Message(), - TRANSITIONS: {("flow", "step_0"): cnd.true()}, + TRANSITIONS: [Tr(dst=("flow", "step_0"))], }, - "fallback": {RESPONSE: Message("the end")}, + "fallback": {RESPONSE: "the end"}, }, GLOBAL: { MISC: { @@ -61,34 +66,22 @@ def custom_response(ctx: Context, _: Pipeline) -> Message: "flow": { LOCAL: { MISC: { - "var2": "rewrite_by_local", - "var3": "rewrite_by_local", - } + "var2": "global data is overwritten by local", + "var3": "global data is overwritten by local", + }, + TRANSITIONS: [Tr(dst=dst.Forward(loop=True))], }, "step_0": { - MISC: {"var3": "info_of_step_0"}, - RESPONSE: custom_response, - TRANSITIONS: {lbl.forward(): cnd.true()}, + MISC: {"var3": "this overwrites local values - step_0"}, + RESPONSE: CustomResponse(), }, "step_1": { - MISC: {"var3": "info_of_step_1"}, - RESPONSE: custom_response, - TRANSITIONS: {lbl.forward(): cnd.true()}, + MISC: {"var3": "this overwrites local values - step_1"}, + RESPONSE: CustomResponse(), }, "step_2": { - MISC: {"var3": "info_of_step_2"}, - RESPONSE: custom_response, - TRANSITIONS: {lbl.forward(): cnd.true()}, - }, - "step_3": { - MISC: {"var3": "info_of_step_3"}, - RESPONSE: custom_response, - TRANSITIONS: {lbl.forward(): cnd.true()}, - }, - "step_4": { - MISC: {"var3": "info_of_step_4"}, - RESPONSE: custom_response, - TRANSITIONS: {"step_0": cnd.true()}, + MISC: {"var3": "this overwrites local values - step_2"}, + RESPONSE: CustomResponse(), }, }, } @@ -98,45 +91,31 @@ def custom_response(ctx: Context, _: Pipeline) -> Message: happy_path = ( ( Message(), - "ctx.last_label=('flow', 'step_0'): current_node.misc=" - "{'var1': 'global_data', " - "'var2': 'rewrite_by_local', " - "'var3': 'info_of_step_0'}", - ), - ( - Message(), - "ctx.last_label=('flow', 'step_1'): current_node.misc=" - "{'var1': 'global_data', " - "'var2': 'rewrite_by_local', " - "'var3': 'info_of_step_1'}", - ), - ( - Message(), - "ctx.last_label=('flow', 'step_2'): current_node.misc=" + "node_name=step_0: current_node.misc=" "{'var1': 'global_data', " - "'var2': 'rewrite_by_local', " - "'var3': 'info_of_step_2'}", + "'var2': 'global data is overwritten by local', " + "'var3': 'this overwrites local values - step_0'}", ), ( Message(), - "ctx.last_label=('flow', 'step_3'): current_node.misc=" + "node_name=step_1: current_node.misc=" "{'var1': 'global_data', " - "'var2': 'rewrite_by_local', " - "'var3': 'info_of_step_3'}", + "'var2': 'global data is overwritten by local', " + "'var3': 'this overwrites local values - step_1'}", ), ( Message(), - "ctx.last_label=('flow', 'step_4'): current_node.misc=" + "node_name=step_2: current_node.misc=" "{'var1': 'global_data', " - "'var2': 'rewrite_by_local', " - "'var3': 'info_of_step_4'}", + "'var2': 'global data is overwritten by local', " + "'var3': 'this overwrites local values - step_2'}", ), ( Message(), - "ctx.last_label=('flow', 'step_0'): current_node.misc=" + "node_name=step_0: current_node.misc=" "{'var1': 'global_data', " - "'var2': 'rewrite_by_local', " - "'var3': 'info_of_step_0'}", + "'var2': 'global data is overwritten by local', " + "'var3': 'this overwrites local values - step_0'}", ), ) @@ -149,6 +128,6 @@ def custom_response(ctx: Context, _: Pipeline) -> Message: ) if __name__ == "__main__": - check_happy_path(pipeline, happy_path) + check_happy_path(pipeline, happy_path, printout=True) if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/script/core/9_pre_transition_processing.py b/tutorials/script/core/9_pre_transition_processing.py new file mode 100644 index 000000000..86c69fc41 --- /dev/null +++ b/tutorials/script/core/9_pre_transition_processing.py @@ -0,0 +1,139 @@ +# %% [markdown] +""" +# Core: 9. Pre-transition processing + +Here, %mddoclink(api,core.script,PRE_TRANSITION) +is demonstrated which can be used for additional context +processing before transitioning to the next step. +""" + +# %pip install chatsky + +# %% +from chatsky import ( + GLOBAL, + RESPONSE, + TRANSITIONS, + PRE_RESPONSE, + PRE_TRANSITION, + Context, + Pipeline, + BaseProcessing, + BaseResponse, + MessageInitTypes, + Transition as Tr, + destinations as dst, + processing as proc, +) + +from chatsky.utils.testing.common import ( + check_happy_path, + is_interactive_mode, +) + + +# %% [markdown] +""" +Processing functions can be used at two stages: + +1. Pre-transition. Triggers after response is received but before + the next node is considered. +2. Pre-response. Triggers after transition is chosen and current node is + changed but before response of that node is calculated. + +In this tutorial we'll save the response function of the current node +during pre-transition and extract it during pre-response +(at which point current node is already changed). +""" + + +# %% +class SavePreviousNodeResponse(BaseProcessing): + async def call(self, ctx: Context) -> None: + if ctx.current_node.response is not None: + ctx.misc["previous_node_response"] = ctx.current_node.response + # This function is called as Pre-transition + # so current node is going to be the previous one + # when we reach the Pre-response step + + +class PrependPreviousNodeResponse(proc.ModifyResponse): + async def modified_response( + self, original_response: BaseResponse, ctx: Context + ) -> MessageInitTypes: + result = await original_response(ctx) + + previous_node_response = ctx.misc.get("previous_node_response") + if previous_node_response is None: + return result + else: + previous_result = await previous_node_response(ctx) + return f"previous={previous_result.text}: current={result.text}" + + +# %% [markdown] +""" +
+ +Note + +Previous node can be accessed another way. + +Instead of storing the node response in misc, +one can obtain previous label +with `dst.Previous()(ctx)` and then get the node from the +%mddoclink(api,core.script,Script) object: + +```python +ctx.pipeline.script.get_inherited_node(dst.Previous()(ctx)) +``` + +
+""" + + +# %% +# a dialog script +toy_script = { + "root": { + "start": { + TRANSITIONS: [Tr(dst=("flow", "step_0"))], + }, + "fallback": {RESPONSE: "the end"}, + }, + GLOBAL: { + PRE_RESPONSE: {"proc_name_1": PrependPreviousNodeResponse()}, + PRE_TRANSITION: {"proc_name_1": SavePreviousNodeResponse()}, + TRANSITIONS: [Tr(dst=dst.Forward(loop=True))], + }, + "flow": { + "step_0": {RESPONSE: "first"}, + "step_1": {RESPONSE: "second"}, + "step_2": {RESPONSE: "third"}, + "step_3": {RESPONSE: "fourth"}, + "step_4": {RESPONSE: "fifth"}, + }, +} + + +# testing +happy_path = ( + ("1", "first"), + ("2", "previous=first: current=second"), + ("3", "previous=second: current=third"), + ("4", "previous=third: current=fourth"), + ("5", "previous=fourth: current=fifth"), +) + + +# %% +pipeline = Pipeline( + script=toy_script, + start_label=("root", "start"), + fallback_label=("root", "fallback"), +) + +if __name__ == "__main__": + check_happy_path(pipeline, happy_path, printout=True) + if is_interactive_mode(): + pipeline.run() diff --git a/tutorials/script/core/9_pre_transitions_processing.py b/tutorials/script/core/9_pre_transitions_processing.py deleted file mode 100644 index e99fffdb3..000000000 --- a/tutorials/script/core/9_pre_transitions_processing.py +++ /dev/null @@ -1,99 +0,0 @@ -# %% [markdown] -""" -# Core: 9. Pre-transitions processing - -This tutorial shows pre-transitions processing feature. - -Here, %mddoclink(api,script.core.keywords,Keywords.PRE_TRANSITIONS_PROCESSING) -is demonstrated which can be used for additional context -processing before transitioning to the next step. - -First of all, let's do all the necessary imports from Chatsky. -""" - -# %pip install chatsky - -# %% -from chatsky.script import ( - GLOBAL, - RESPONSE, - TRANSITIONS, - PRE_RESPONSE_PROCESSING, - PRE_TRANSITIONS_PROCESSING, - Context, - Message, -) -import chatsky.script.labels as lbl -import chatsky.script.conditions as cnd -from chatsky.pipeline import Pipeline -from chatsky.utils.testing.common import ( - check_happy_path, - is_interactive_mode, - run_interactive_mode, -) - - -# %% -def save_previous_node_response(ctx: Context, _: Pipeline): - processed_node = ctx.current_node - ctx.misc["previous_node_response"] = processed_node.response - - -def prepend_previous_node_response(ctx: Context, _: Pipeline): - processed_node = ctx.current_node - processed_node.response = Message( - text=f"previous={ctx.misc['previous_node_response'].text}:" - f" current={processed_node.response.text}" - ) - - -# %% -# a dialog script -toy_script = { - "root": { - "start": { - RESPONSE: Message(), - TRANSITIONS: {("flow", "step_0"): cnd.true()}, - }, - "fallback": {RESPONSE: Message("the end")}, - }, - GLOBAL: { - PRE_RESPONSE_PROCESSING: { - "proc_name_1": prepend_previous_node_response - }, - PRE_TRANSITIONS_PROCESSING: { - "proc_name_1": save_previous_node_response - }, - TRANSITIONS: {lbl.forward(0.1): cnd.true()}, - }, - "flow": { - "step_0": {RESPONSE: Message("first")}, - "step_1": {RESPONSE: Message("second")}, - "step_2": {RESPONSE: Message("third")}, - "step_3": {RESPONSE: Message("fourth")}, - "step_4": {RESPONSE: Message("fifth")}, - }, -} - - -# testing -happy_path = ( - ("1", "previous=None: current=first"), - ("2", "previous=first: current=second"), - ("3", "previous=second: current=third"), - ("4", "previous=third: current=fourth"), - ("5", "previous=fourth: current=fifth"), -) - - -# %% -pipeline = Pipeline( - script=toy_script, - start_label=("root", "start"), - fallback_label=("root", "fallback"), -) - -if __name__ == "__main__": - check_happy_path(pipeline, happy_path) - if is_interactive_mode(): - run_interactive_mode(pipeline) diff --git a/tutorials/script/responses/1_basics.py b/tutorials/script/responses/1_basics.py deleted file mode 100644 index 4202af96c..000000000 --- a/tutorials/script/responses/1_basics.py +++ /dev/null @@ -1,106 +0,0 @@ -# %% [markdown] -""" -# Responses: 1. Basics - -Here, the process of response forming is shown. -Special keywords %mddoclink(api,script.core.keywords,Keywords.RESPONSE) -and %mddoclink(api,script.core.keywords,Keywords.TRANSITIONS) -are used for that. -""" - -# %pip install chatsky - -# %% -from typing import NamedTuple - -from chatsky.script import Message -from chatsky.script.conditions import exact_match -from chatsky.script import RESPONSE, TRANSITIONS -from chatsky.pipeline import Pipeline -from chatsky.utils.testing import ( - check_happy_path, - is_interactive_mode, - run_interactive_mode, -) - - -# %% -toy_script = { - "greeting_flow": { - "start_node": { - RESPONSE: Message(""), - TRANSITIONS: {"node1": exact_match("Hi")}, - }, - "node1": { - RESPONSE: Message("Hi, how are you?"), - TRANSITIONS: {"node2": exact_match("i'm fine, how are you?")}, - }, - "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: {"node3": exact_match("Let's talk about music.")}, - }, - "node3": { - RESPONSE: Message("Sorry, I can not talk about music now."), - TRANSITIONS: {"node4": exact_match("Ok, goodbye.")}, - }, - "node4": { - RESPONSE: Message("bye"), - TRANSITIONS: {"node1": exact_match("Hi")}, - }, - "fallback_node": { - RESPONSE: Message("Ooops"), - TRANSITIONS: {"node1": exact_match("Hi")}, - }, - } -} - -happy_path = ( - (Message("Hi"), Message("Hi, how are you?")), - ( - Message("i'm fine, how are you?"), - Message("Good. What do you want to talk about?"), - ), - ( - Message("Let's talk about music."), - Message("Sorry, I can not talk about music now."), - ), - (Message("Ok, goodbye."), Message("bye")), - (Message("Hi"), Message("Hi, how are you?")), - (Message("stop"), Message("Ooops")), - (Message("stop"), Message("Ooops")), - (Message("Hi"), Message("Hi, how are you?")), - ( - Message("i'm fine, how are you?"), - Message("Good. What do you want to talk about?"), - ), - ( - Message("Let's talk about music."), - Message("Sorry, I can not talk about music now."), - ), - (Message("Ok, goodbye."), Message("bye")), -) - - -# %% -class CallbackRequest(NamedTuple): - payload: str - - -# %% -pipeline = Pipeline( - script=toy_script, - start_label=("greeting_flow", "start_node"), - fallback_label=("greeting_flow", "fallback_node"), -) - -if __name__ == "__main__": - check_happy_path( - pipeline, - happy_path, - ) # This is a function for automatic tutorial running - # (testing) with `happy_path` - - # This runs tutorial in interactive mode if not in IPython env - # and if `DISABLE_INTERACTIVE_MODE` is not set - if is_interactive_mode(): - run_interactive_mode(pipeline) # This runs tutorial in interactive mode diff --git a/tutorials/script/responses/1_media.py b/tutorials/script/responses/1_media.py new file mode 100644 index 000000000..7ecf86fa4 --- /dev/null +++ b/tutorials/script/responses/1_media.py @@ -0,0 +1,112 @@ +# %% [markdown] +""" +# Responses: 1. Media + +Here, %mddoclink(api,core.message,Attachment) class is shown. +Attachments can be used for attaching different media elements +(such as %mddoclink(api,core.message,Image), +%mddoclink(api,core.message,Document) +or %mddoclink(api,core.message,Audio)). + +They can be attached to any message but will only work if the chosen +[messenger interface](%doclink(api,index_messenger_interfaces)) supports them. +""" + +# %pip install chatsky + +# %% +from chatsky import ( + RESPONSE, + TRANSITIONS, + Message, + Pipeline, + Transition as Tr, + conditions as cnd, + destinations as dst, +) +from chatsky.core.message import Image + +from chatsky.utils.testing import ( + check_happy_path, + is_interactive_mode, +) + + +# %% +img_url = "https://www.python.org/static/img/python-logo.png" +toy_script = { + "root": { + "start": { + TRANSITIONS: [Tr(dst=("pics", "ask_picture"))], + }, + "fallback": { + RESPONSE: "Final node reached, send any message to restart.", + TRANSITIONS: [Tr(dst=("pics", "ask_picture"))], + }, + }, + "pics": { + "ask_picture": { + RESPONSE: "Please, send me a picture url", + TRANSITIONS: [ + Tr( + dst=("pics", "send_one"), + cnd=cnd.Regexp(r"^http.+\.png$"), + ), + Tr( + dst=("pics", "send_many"), + cnd=cnd.Regexp(f"{img_url} repeat 10 times"), + ), + Tr( + dst=dst.Current(), + ), + ], + }, + "send_one": { + RESPONSE: Message( # need to use the Message class to send images + text="here's my picture!", + attachments=[Image(source=img_url)], + ), + }, + "send_many": { + RESPONSE: Message( + text="Look at my pictures", + attachments=[Image(source=img_url)] * 10, + ), + }, + }, +} + +happy_path = ( + ("Hi", "Please, send me a picture url"), + ("no", "Please, send me a picture url"), + ( + img_url, + Message( + text="here's my picture!", + attachments=[Image(source=img_url)], + ), + ), + ("ok", "Final node reached, send any message to restart."), + ("ok", "Please, send me a picture url"), + ( + f"{img_url} repeat 10 times", + Message( + text="Look at my pictures", + attachments=[Image(source=img_url)] * 10, + ), + ), + ("ok", "Final node reached, send any message to restart."), +) + + +# %% +pipeline = Pipeline( + script=toy_script, + start_label=("root", "start"), + fallback_label=("root", "fallback"), +) + +if __name__ == "__main__": + check_happy_path(pipeline, happy_path, printout=True) + if is_interactive_mode(): + pipeline.run() diff --git a/tutorials/script/responses/2_media.py b/tutorials/script/responses/2_media.py deleted file mode 100644 index ed5f9c288..000000000 --- a/tutorials/script/responses/2_media.py +++ /dev/null @@ -1,128 +0,0 @@ -# %% [markdown] -""" -# Responses: 2. Media - -Here, %mddoclink(api,script.core.message,Attachment) class is shown. -Attachments can be used for attaching different media elements -(such as %mddoclink(api,script.core.message,Image), -%mddoclink(api,script.core.message,Document) -or %mddoclink(api,script.core.message,Audio)). - -They can be attached to any message but will only work if the chosen -[messenger interface](%doclink(api,index_messenger_interfaces)) supports them. -""" - -# %pip install chatsky - -# %% -from chatsky.script import RESPONSE, TRANSITIONS -from chatsky.script.conditions import std_conditions as cnd - -from chatsky.script.core.message import Image, Message - -from chatsky.pipeline import Pipeline -from chatsky.utils.testing import ( - check_happy_path, - is_interactive_mode, - run_interactive_mode, -) - - -# %% -img_url = "https://www.python.org/static/img/python-logo.png" -toy_script = { - "root": { - "start": { - RESPONSE: Message(""), - TRANSITIONS: {("pics", "ask_picture"): cnd.true()}, - }, - "fallback": { - RESPONSE: Message( - text="Final node reached, send any message to restart." - ), - TRANSITIONS: {("pics", "ask_picture"): cnd.true()}, - }, - }, - "pics": { - "ask_picture": { - RESPONSE: Message("Please, send me a picture url"), - TRANSITIONS: { - ("pics", "send_one", 1.1): cnd.regexp(r"^http.+\.png$"), - ("pics", "send_many", 1.0): cnd.regexp( - f"{img_url} repeat 10 times" - ), - ("pics", "repeat", 0.9): cnd.true(), - }, - }, - "send_one": { - RESPONSE: Message( - text="here's my picture!", - attachments=[Image(source=img_url)], - ), - TRANSITIONS: {("root", "fallback"): cnd.true()}, - }, - "send_many": { - RESPONSE: Message( - text="Look at my pictures", - attachments=[Image(source=img_url)], - ), - TRANSITIONS: {("root", "fallback"): cnd.true()}, - }, - "repeat": { - RESPONSE: Message( - text="I cannot find the picture. Please, try again." - ), - TRANSITIONS: { - ("pics", "send_one", 1.1): cnd.regexp(r"^http.+\.png$"), - ("pics", "send_many", 1.0): cnd.regexp( - r"^http.+\.png repeat 10 times" - ), - ("pics", "repeat", 0.9): cnd.true(), - }, - }, - }, -} - -happy_path = ( - (Message("Hi"), Message("Please, send me a picture url")), - ( - Message("no"), - Message("I cannot find the picture. Please, try again."), - ), - ( - Message(img_url), - Message( - text="here's my picture!", - attachments=[Image(source=img_url)], - ), - ), - ( - Message("ok"), - Message("Final node reached, send any message to restart."), - ), - (Message("ok"), Message("Please, send me a picture url")), - ( - Message(f"{img_url} repeat 10 times"), - Message( - text="Look at my pictures", - attachments=[Image(source=img_url)], - ), - ), - ( - Message("ok"), - Message("Final node reached, send any message to restart."), - ), -) - - -# %% -pipeline = Pipeline( - script=toy_script, - start_label=("root", "start"), - fallback_label=("root", "fallback"), -) - -if __name__ == "__main__": - check_happy_path(pipeline, happy_path) - if is_interactive_mode(): - run_interactive_mode(pipeline) diff --git a/tutorials/script/responses/2_multi_message.py b/tutorials/script/responses/2_multi_message.py new file mode 100644 index 000000000..f6019f3a2 --- /dev/null +++ b/tutorials/script/responses/2_multi_message.py @@ -0,0 +1,156 @@ +# %% [markdown] +""" +# Responses: 2. Multi Message + +This tutorial shows how to store several messages inside a single one. +This might be useful if you want Chatsky Pipeline to send `response` candidates +to the messenger interface instead of a final response. +""" + +# %pip install chatsky + +# %% + +from chatsky import ( + TRANSITIONS, + RESPONSE, + Message, + Pipeline, + Transition as Tr, + conditions as cnd, +) + +from chatsky.utils.testing.common import ( + check_happy_path, + is_interactive_mode, +) + +# %% +toy_script = { + "greeting_flow": { + "start_node": { + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], + }, + "node1": { + RESPONSE: Message( + misc={ + "messages": [ + Message( + text="Hi, what is up?", misc={"confidences": 0.85} + ), + Message( + text="Hello, how are you?", + misc={"confidences": 0.9}, + ), + ] + } + ), + TRANSITIONS: [ + Tr(dst="node2", cnd=cnd.ExactMatch("I'm fine, how are you?")) + ], + }, + "node2": { + RESPONSE: "Good. What do you want to talk about?", + TRANSITIONS: [ + Tr(dst="node3", cnd=cnd.ExactMatch("Let's talk about music.")) + ], + }, + "node3": { + RESPONSE: "Sorry, I can not talk about that now.", + TRANSITIONS: [Tr(dst="node4", cnd=cnd.ExactMatch("Ok, goodbye."))], + }, + "node4": { + RESPONSE: "bye", + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], + }, + "fallback_node": { + RESPONSE: "Ooops", + TRANSITIONS: [Tr(dst="node1", cnd=cnd.ExactMatch("Hi"))], + }, + } +} + +# testing +happy_path = ( + ( + "Hi", + Message( + misc={ + "messages": [ + Message("Hi, what is up?", misc={"confidences": 0.85}), + Message("Hello, how are you?", misc={"confidences": 0.9}), + ] + } + ), + ), # start_node -> node1 + ( + "I'm fine, how are you?", + "Good. What do you want to talk about?", + ), # node1 -> node2 + ( + "Let's talk about music.", + "Sorry, I can not talk about that now.", + ), # node2 -> node3 + ("Ok, goodbye.", "bye"), # node3 -> node4 + ( + "Hi", + Message( + misc={ + "messages": [ + Message("Hi, what is up?", misc={"confidences": 0.85}), + Message("Hello, how are you?", misc={"confidences": 0.9}), + ] + } + ), + ), # node4 -> node1 + ( + "stop", + "Ooops", + ), + # node1 -> fallback_node + ( + "one", + "Ooops", + ), # f_n->f_n + ( + "help", + "Ooops", + ), # f_n->f_n + ( + "nope", + "Ooops", + ), # f_n->f_n + ( + "Hi", + Message( + misc={ + "messages": [ + Message("Hi, what is up?", misc={"confidences": 0.85}), + Message("Hello, how are you?", misc={"confidences": 0.9}), + ] + } + ), + ), # fallback_node -> node1 + ( + "I'm fine, how are you?", + "Good. What do you want to talk about?", + ), # node1 -> node2 + ( + "Let's talk about music.", + "Sorry, I can not talk about that now.", + ), # node2 -> node3 + ("Ok, goodbye.", "bye"), # node3 -> node4 +) + +# %% + +pipeline = Pipeline( + script=toy_script, + start_label=("greeting_flow", "start_node"), + fallback_label=("greeting_flow", "fallback_node"), +) + +if __name__ == "__main__": + check_happy_path(pipeline, happy_path, printout=True) + if is_interactive_mode(): + pipeline.run() diff --git a/tutorials/script/responses/3_multi_message.py b/tutorials/script/responses/3_multi_message.py deleted file mode 100644 index 1f24a7be8..000000000 --- a/tutorials/script/responses/3_multi_message.py +++ /dev/null @@ -1,156 +0,0 @@ -# %% [markdown] -""" -# Responses: 3. Multi Message - -This tutorial shows how to store several messages inside a single one. -This might be useful if you want Chatsky Pipeline to send `response` candidates -to the messenger interface instead of a final response. - -However, this approach is not recommended due to history incompleteness. -""" - -# %pip install chatsky - -# %% - -from chatsky.script import TRANSITIONS, RESPONSE, Message -import chatsky.script.conditions as cnd - -from chatsky.pipeline import Pipeline -from chatsky.utils.testing.common import ( - check_happy_path, - is_interactive_mode, - run_interactive_mode, -) - -# %% -toy_script = { - "greeting_flow": { - "start_node": { # This is an initial node, - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, - # If "Hi" == request of user then we make the transition - }, - "node1": { - RESPONSE: Message( - misc={ - "messages": [ - Message("Hi, what is up?", misc={"confidences": 0.85}), - Message( - text="Hello, how are you?", - misc={"confidences": 0.9}, - ), - ] - } - ), - TRANSITIONS: {"node2": cnd.exact_match("I'm fine, how are you?")}, - }, - "node2": { - RESPONSE: Message("Good. What do you want to talk about?"), - TRANSITIONS: {"node3": cnd.exact_match("Let's talk about music.")}, - }, - "node3": { - RESPONSE: Message("Sorry, I can not talk about that now."), - TRANSITIONS: {"node4": cnd.exact_match("Ok, goodbye.")}, - }, - "node4": { - RESPONSE: Message("bye"), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, - }, - "fallback_node": { # We get to this node - # if an error occurred while the agent was running. - RESPONSE: Message("Ooops"), - TRANSITIONS: {"node1": cnd.exact_match("Hi")}, - }, - } -} - -# testing -happy_path = ( - ( - Message("Hi"), - Message( - misc={ - "messages": [ - Message("Hi, what is up?", misc={"confidences": 0.85}), - Message( - text="Hello, how are you?", misc={"confidences": 0.9} - ), - ] - } - ), - ), # start_node -> node1 - ( - Message("I'm fine, how are you?"), - Message("Good. What do you want to talk about?"), - ), # node1 -> node2 - ( - Message("Let's talk about music."), - Message("Sorry, I can not talk about that now."), - ), # node2 -> node3 - (Message("Ok, goodbye."), Message("bye")), # node3 -> node4 - ( - Message("Hi"), - Message( - misc={ - "messages": [ - Message("Hi, what is up?", misc={"confidences": 0.85}), - Message( - text="Hello, how are you?", misc={"confidences": 0.9} - ), - ] - } - ), - ), # node4 -> node1 - ( - Message("stop"), - Message("Ooops"), - ), - # node1 -> fallback_node - ( - Message("one"), - Message("Ooops"), - ), # f_n->f_n - ( - Message("help"), - Message("Ooops"), - ), # f_n->f_n - ( - Message("nope"), - Message("Ooops"), - ), # f_n->f_n - ( - Message("Hi"), - Message( - misc={ - "messages": [ - Message("Hi, what is up?", misc={"confidences": 0.85}), - Message( - text="Hello, how are you?", misc={"confidences": 0.9} - ), - ] - } - ), - ), # fallback_node -> node1 - ( - Message("I'm fine, how are you?"), - Message("Good. What do you want to talk about?"), - ), # node1 -> node2 - ( - Message("Let's talk about music."), - Message("Sorry, I can not talk about that now."), - ), # node2 -> node3 - (Message("Ok, goodbye."), Message("bye")), # node3 -> node4 -) - -# %% - -pipeline = Pipeline( - script=toy_script, - start_label=("greeting_flow", "start_node"), - fallback_label=("greeting_flow", "fallback_node"), -) - -if __name__ == "__main__": - check_happy_path(pipeline, happy_path) - if is_interactive_mode(): - run_interactive_mode(pipeline) diff --git a/tutorials/slots/1_basic_example.py b/tutorials/slots/1_basic_example.py index 1c528de52..66e8cedcb 100644 --- a/tutorials/slots/1_basic_example.py +++ b/tutorials/slots/1_basic_example.py @@ -9,27 +9,25 @@ # %pip install chatsky # %% -from chatsky.script import conditions as cnd -from chatsky.script import ( +from chatsky import ( RESPONSE, TRANSITIONS, - PRE_TRANSITIONS_PROCESSING, - PRE_RESPONSE_PROCESSING, + PRE_TRANSITION, + PRE_RESPONSE, GLOBAL, LOCAL, - Message, + Pipeline, + Transition as Tr, + conditions as cnd, + processing as proc, + responses as rsp, ) -from chatsky.pipeline import Pipeline -from chatsky.slots import GroupSlot, RegexpSlot -from chatsky.slots import processing as slot_procs -from chatsky.slots import response as slot_rsp -from chatsky.slots import conditions as slot_cnd +from chatsky.slots import RegexpSlot from chatsky.utils.testing import ( check_happy_path, is_interactive_mode, - run_interactive_mode, ) # %% [markdown] @@ -55,42 +53,42 @@ """ # %% -SLOTS = GroupSlot( - person=GroupSlot( - username=RegexpSlot( +SLOTS = { + "person": { + "username": RegexpSlot( regexp=r"username is ([a-zA-Z]+)", match_group_idx=1, ), - email=RegexpSlot( + "email": RegexpSlot( regexp=r"email is ([a-z@\.A-Z]+)", match_group_idx=1, ), - ), - friend=GroupSlot( - first_name=RegexpSlot(regexp=r"^[A-Z][a-z]+?(?= )"), - last_name=RegexpSlot(regexp=r"(?<= )[A-Z][a-z]+"), - ), -) + }, + "friend": { + "first_name": RegexpSlot(regexp=r"^[A-Z][a-z]+?(?= )"), + "last_name": RegexpSlot(regexp=r"(?<= )[A-Z][a-z]+"), + }, +} # %% [markdown] """ The slots module provides several functions for managing slots in-script: -- %mddoclink(api,slots.conditions,slots_extracted): +- %mddoclink(api,conditions.slots,SlotsExtracted): Condition for checking if specified slots are extracted. -- %mddoclink(api,slots.processing,extract): +- %mddoclink(api,processing.slots,Extract): A processing function that extracts specified slots. -- %mddoclink(api,slots.processing,extract_all): +- %mddoclink(api,processing.slots,ExtractAll): A processing function that extracts all slots. -- %mddoclink(api,slots.processing,unset): +- %mddoclink(api,processing.slots,Unset): A processing function that marks specified slots as not extracted, effectively resetting their state. -- %mddoclink(api,slots.processing,unset_all): +- %mddoclink(api,processing.slots,UnsetAll): A processing function that marks all slots as not extracted. -- %mddoclink(api,slots.processing,fill_template): +- %mddoclink(api,processing.slots,FillTemplate): A processing function that fills the `response` Message text with extracted slot values. -- %mddoclink(api,slots.response,filled_template): +- %mddoclink(api,responses.slots,FilledTemplate): A response function that takes a Message with a format-string text and returns Message with its text string filled with extracted slot values. @@ -100,120 +98,101 @@ # %% script = { - GLOBAL: {TRANSITIONS: {("username_flow", "ask"): cnd.regexp(r"^[sS]tart")}}, + GLOBAL: { + TRANSITIONS: [ + Tr(dst=("username_flow", "ask"), cnd=cnd.Regexp(r"^[sS]tart")) + ] + }, "username_flow": { LOCAL: { - PRE_TRANSITIONS_PROCESSING: { - "get_slot": slot_procs.extract("person.username") - }, - TRANSITIONS: { - ("email_flow", "ask", 1.2): slot_cnd.slots_extracted( - "person.username" + PRE_TRANSITION: {"get_slot": proc.Extract("person.username")}, + TRANSITIONS: [ + Tr( + dst=("email_flow", "ask"), + cnd=cnd.SlotsExtracted("person.username"), + priority=1.2, ), - ("username_flow", "repeat_question", 0.8): cnd.true(), - }, + Tr(dst=("username_flow", "repeat_question"), priority=0.8), + ], }, "ask": { - RESPONSE: Message(text="Write your username (my username is ...):"), + RESPONSE: "Write your username (my username is ...):", }, "repeat_question": { - RESPONSE: Message( - text="Please, type your username again (my username is ...):" - ) + RESPONSE: "Please, type your username again (my username is ...):", }, }, "email_flow": { LOCAL: { - PRE_TRANSITIONS_PROCESSING: { - "get_slot": slot_procs.extract("person.email") - }, - TRANSITIONS: { - ("friend_flow", "ask", 1.2): slot_cnd.slots_extracted( - "person.username", "person.email" + PRE_TRANSITION: {"get_slot": proc.Extract("person.email")}, + TRANSITIONS: [ + Tr( + dst=("friend_flow", "ask"), + cnd=cnd.SlotsExtracted("person.username", "person.email"), + priority=1.2, ), - ("email_flow", "repeat_question", 0.8): cnd.true(), - }, + Tr(dst=("email_flow", "repeat_question"), priority=0.8), + ], }, "ask": { - RESPONSE: Message(text="Write your email (my email is ...):"), + RESPONSE: "Write your email (my email is ...):", }, "repeat_question": { - RESPONSE: Message( - text="Please, write your email again (my email is ...):" - ) + RESPONSE: "Please, write your email again (my email is ...):", }, }, "friend_flow": { LOCAL: { - PRE_TRANSITIONS_PROCESSING: { - "get_slots": slot_procs.extract("friend") - }, - TRANSITIONS: { - ("root", "utter", 1.2): slot_cnd.slots_extracted( - "friend.first_name", "friend.last_name", mode="any" + PRE_TRANSITION: {"get_slots": proc.Extract("friend")}, + TRANSITIONS: [ + Tr( + dst=("root", "utter"), + cnd=cnd.SlotsExtracted( + "friend.first_name", "friend.last_name", mode="any" + ), + priority=1.2, ), - ("friend_flow", "repeat_question", 0.8): cnd.true(), - }, - }, - "ask": { - RESPONSE: Message( - text="Please, name me one of your friends: (John Doe)" - ) + Tr(dst=("friend_flow", "repeat_question"), priority=0.8), + ], }, + "ask": {RESPONSE: "Please, name me one of your friends: (John Doe)"}, "repeat_question": { - RESPONSE: Message( - text="Please, name me one of your friends again: (John Doe)" - ) + RESPONSE: "Please, name me one of your friends again: (John Doe)" }, }, "root": { "start": { - RESPONSE: Message(text=""), - TRANSITIONS: {("username_flow", "ask"): cnd.true()}, + TRANSITIONS: [Tr(dst=("username_flow", "ask"))], }, "fallback": { - RESPONSE: Message(text="Finishing query"), - TRANSITIONS: {("username_flow", "ask"): cnd.true()}, + RESPONSE: "Finishing query", + TRANSITIONS: [Tr(dst=("username_flow", "ask"))], }, "utter": { - RESPONSE: slot_rsp.filled_template( - Message( - text="Your friend is {friend.first_name} {friend.last_name}" - ) + RESPONSE: rsp.FilledTemplate( + "Your friend is {friend.first_name} {friend.last_name}" ), - TRANSITIONS: {("root", "utter_alternative"): cnd.true()}, + TRANSITIONS: [Tr(dst=("root", "utter_alternative"))], }, "utter_alternative": { - RESPONSE: Message( - text="Your username is {person.username}. " - "Your email is {person.email}." - ), - PRE_RESPONSE_PROCESSING: {"fill": slot_procs.fill_template()}, - TRANSITIONS: {("root", "fallback"): cnd.true()}, + RESPONSE: "Your username is {person.username}. " + "Your email is {person.email}.", + PRE_RESPONSE: {"fill": proc.FillTemplate()}, }, }, } # %% HAPPY_PATH = [ + ("hi", "Write your username (my username is ...):"), + ("my username is groot", "Write your email (my email is ...):"), ( - Message(text="hi"), - Message(text="Write your username (my username is ...):"), - ), - ( - Message(text="my username is groot"), - Message(text="Write your email (my email is ...):"), - ), - ( - Message(text="my email is groot@gmail.com"), - Message(text="Please, name me one of your friends: (John Doe)"), - ), - (Message(text="Bob Page"), Message(text="Your friend is Bob Page")), - ( - Message(text="ok"), - Message(text="Your username is groot. Your email is groot@gmail.com."), + "my email is groot@gmail.com", + "Please, name me one of your friends: (John Doe)", ), - (Message(text="ok"), Message(text="Finishing query")), + ("Bob Page", "Your friend is Bob Page"), + ("ok", "Your username is groot. Your email is groot@gmail.com."), + ("ok", "Finishing query"), ] # %% @@ -226,11 +205,9 @@ if __name__ == "__main__": check_happy_path( - pipeline, HAPPY_PATH + pipeline, HAPPY_PATH, printout=True ) # This is a function for automatic tutorial running # (testing) with HAPPY_PATH - # This runs tutorial in interactive mode if not in IPython env - # and if `DISABLE_INTERACTIVE_MODE` is not set if is_interactive_mode(): - run_interactive_mode(pipeline) + pipeline.run() diff --git a/tutorials/stats/1_extractor_functions.py b/tutorials/stats/1_extractor_functions.py index a68d3f3fa..5ad50ad34 100644 --- a/tutorials/stats/1_extractor_functions.py +++ b/tutorials/stats/1_extractor_functions.py @@ -46,13 +46,12 @@ # %% import asyncio -from chatsky.pipeline import ( - Pipeline, +from chatsky.core.service import ( ExtraHandlerRuntimeInfo, GlobalExtraHandlerType, to_service, ) -from chatsky.script import Context +from chatsky import Context, Pipeline from chatsky.stats import OtelInstrumentor, default_extractors from chatsky.utils.testing import is_interactive_mode, check_happy_path from chatsky.utils.testing.toy_script import TOY_SCRIPT, HAPPY_PATH @@ -129,6 +128,6 @@ async def heavy_service(ctx: Context): GlobalExtraHandlerType.BEFORE, default_extractors.get_current_label ) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): pipeline.run() diff --git a/tutorials/stats/2_pipeline_integration.py b/tutorials/stats/2_pipeline_integration.py index 928650f36..c72c98e1a 100644 --- a/tutorials/stats/2_pipeline_integration.py +++ b/tutorials/stats/2_pipeline_integration.py @@ -29,13 +29,12 @@ # %% import asyncio -from chatsky.pipeline import ( - Pipeline, +from chatsky.core.service import ( ExtraHandlerRuntimeInfo, ServiceGroup, GlobalExtraHandlerType, ) -from chatsky.script import Context +from chatsky import Context, Pipeline from chatsky.stats import OTLPLogExporter, OTLPSpanExporter from chatsky.stats import ( OtelInstrumentor, @@ -135,6 +134,6 @@ async def heavy_service(ctx: Context): pipeline.add_global_handler(GlobalExtraHandlerType.AFTER_ALL, get_service_state) if __name__ == "__main__": - check_happy_path(pipeline, HAPPY_PATH) + check_happy_path(pipeline, HAPPY_PATH, printout=True) if is_interactive_mode(): pipeline.run() diff --git a/tutorials/utils/1_cache.py b/tutorials/utils/1_cache.py deleted file mode 100644 index 5c3b6980b..000000000 --- a/tutorials/utils/1_cache.py +++ /dev/null @@ -1,75 +0,0 @@ -# %% [markdown] -""" -# 1. Cache - -In this tutorial use of -%mddoclink(api,utils.turn_caching.singleton_turn_caching,cache) -function is demonstrated. - -This function is used a lot like `functools.cache` function and -helps by saving results of heavy function execution and avoiding recalculation. - -Caches are kept in a library-wide singleton -and are cleared at the end of each turn. -""" - -# %pip install chatsky - -# %% -from chatsky.script.conditions import true -from chatsky.script import Context, TRANSITIONS, RESPONSE, Message -from chatsky.script.labels import repeat -from chatsky.pipeline import Pipeline -from chatsky.utils.turn_caching import cache -from chatsky.utils.testing.common import ( - check_happy_path, - is_interactive_mode, - run_interactive_mode, -) - - -external_data = {"counter": 0} - - -# %% -@cache -def cached_response(_): - """ - This function execution result will be saved - for any set of given argument(s). - If the function will be called again - with the same arguments it will prevent it from execution. - The cached values will be used instead. - The cache is stored in a library-wide singleton, - that is cleared in the end of execution of actor and/or pipeline. - """ - external_data["counter"] += 1 - return external_data["counter"] - - -def response(_: Context, __: Pipeline) -> Message: - return Message( - text=f"{cached_response(1)}-{cached_response(2)}-" - f"{cached_response(1)}-{cached_response(2)}" - ) - - -# %% -toy_script = { - "flow": {"node1": {TRANSITIONS: {repeat(): true()}, RESPONSE: response}} -} - -happy_path = ( - (Message(), "1-2-1-2"), - (Message(), "3-4-3-4"), - (Message(), "5-6-5-6"), -) - -pipeline = Pipeline(script=toy_script, start_label=("flow", "node1")) - - -# %% -if __name__ == "__main__": - check_happy_path(pipeline, happy_path) - if is_interactive_mode(): - run_interactive_mode(pipeline) diff --git a/tutorials/utils/2_lru_cache.py b/tutorials/utils/2_lru_cache.py deleted file mode 100644 index 241a0a2e2..000000000 --- a/tutorials/utils/2_lru_cache.py +++ /dev/null @@ -1,73 +0,0 @@ -# %% [markdown] -""" -# 2. LRU Cache - -In this tutorial use of -%mddoclink(api,utils.turn_caching.singleton_turn_caching,lru_cache) -function is demonstrated. - -This function is used a lot like `functools.lru_cache` function and -helps by saving results of heavy function execution and avoiding recalculation. - -Caches are kept in a library-wide singleton -and are cleared at the end of each turn. - -Maximum size parameter limits the amount of function execution results cached. -""" - -# %pip install chatsky - -# %% -from chatsky.script.conditions import true -from chatsky.script import Context, TRANSITIONS, RESPONSE, Message -from chatsky.script.labels import repeat -from chatsky.pipeline import Pipeline -from chatsky.utils.turn_caching import lru_cache -from chatsky.utils.testing.common import ( - check_happy_path, - is_interactive_mode, - run_interactive_mode, -) - -external_data = {"counter": 0} - - -# %% -@lru_cache(maxsize=2) -def cached_response(_): - """ - This function will work exactly the same as the one from previous - tutorial with only one exception. - Only 2 results will be stored; - when the function will be executed with third arguments set, - the least recent result will be deleted. - """ - external_data["counter"] += 1 - return external_data["counter"] - - -def response(_: Context, __: Pipeline) -> Message: - return Message( - text=f"{cached_response(1)}-{cached_response(2)}-{cached_response(3)}-" - f"{cached_response(2)}-{cached_response(1)}" - ) - - -# %% -toy_script = { - "flow": {"node1": {TRANSITIONS: {repeat(): true()}, RESPONSE: response}} -} - -happy_path = ( - (Message(), "1-2-3-2-4"), - (Message(), "5-6-7-6-8"), - (Message(), "9-10-11-10-12"), -) - -pipeline = Pipeline(script=toy_script, start_label=("flow", "node1")) - -# %% -if __name__ == "__main__": - check_happy_path(pipeline, happy_path) - if is_interactive_mode(): - run_interactive_mode(pipeline) diff --git a/utils/stats/sample_data_provider.py b/utils/stats/sample_data_provider.py index f24cfaf3c..ca380898f 100644 --- a/utils/stats/sample_data_provider.py +++ b/utils/stats/sample_data_provider.py @@ -11,8 +11,8 @@ import random import asyncio from tqdm import tqdm -from chatsky.script import Context, Message -from chatsky.pipeline import Pipeline, Service, ExtraHandlerRuntimeInfo, GlobalExtraHandlerType +from chatsky.core import Context, Message, Pipeline +from chatsky.core.service import Service, ExtraHandlerRuntimeInfo, GlobalExtraHandlerType from chatsky.stats import ( default_extractors, OtelInstrumentor, @@ -78,19 +78,11 @@ async def worker(queue: asyncio.Queue): The client message is chosen randomly from a predetermined set of options. It simulates pauses in between messages by calling the sleep function. - The function also starts a new dialog as a new user, if the current dialog - ended in the fallback_node. - :param queue: Queue for sharing context variables. """ ctx: Context = await queue.get() - label = ctx.last_label if ctx.last_label else pipeline.actor.fallback_label - flow, node = label[:2] - if [flow, node] == ["root", "fallback"]: - await asyncio.sleep(random.random() * 3) - ctx = Context() - flow, node = ["root", "start"] - answers = list(MULTIFLOW_REQUEST_OPTIONS.get(flow, {}).get(node, [])) + label = ctx.last_label + answers = list(MULTIFLOW_REQUEST_OPTIONS.get(label.flow_name, {}).get(label.node_name, [])) in_text = random.choice(answers) if answers else "go to fallback" in_message = Message(in_text) await asyncio.sleep(random.random() * 3) @@ -111,7 +103,7 @@ async def main(n_iterations: int = 100, n_workers: int = 4): ctxs = asyncio.Queue() parallel_iterations = n_iterations // n_workers for _ in range(n_workers): - await ctxs.put(Context()) + await ctxs.put(Context.init(("root", "start"))) for _ in tqdm(range(parallel_iterations)): await asyncio.gather(*(worker(ctxs) for _ in range(n_workers))) diff --git a/utils/test_data_generators/telegram_tutorial_data.py b/utils/test_data_generators/telegram_tutorial_data.py index 9c9a82822..930670a8f 100644 --- a/utils/test_data_generators/telegram_tutorial_data.py +++ b/utils/test_data_generators/telegram_tutorial_data.py @@ -17,7 +17,7 @@ import os from contextlib import contextmanager -from chatsky.script import Message +from chatsky import Message ROOT = Path(__file__).parent.parent.parent From 40981ef76022141b0f47c30d00532dbd3fcc9376 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Sat, 7 Sep 2024 23:08:47 +0300 Subject: [PATCH 06/11] Feat/script parser (#385) # Description Added option to initialize Pipeline from yaml/json files. ## Changelog ### Breaking changes - Removed `proc.ExtractAll` -- the function is unsafe as it overwrites the entire slot storage. it is still available as method of the slot manager ### Features - Add Pipeline from file import - Add function that creates an index of commonly-used Chatsky objects - Added imports to some `__init__` files - Allow initializing NodeLabel from a list of two strings ### Bug fixes - Slot extraction will now not write the value to the slot storage if value was not successfully extracted. Can be changed via the `success_only` flag ### Devel - Add aliases to script keywords # Checklist - [x] I have performed a self-review of the changes # To Consider - Add tests (if functionality is changed) - Update API reference / tutorials / guides - Update CONTRIBUTING.md (if devel workflow is changed) - Update `.ignore` files, scripts (such as `lint`), distribution manifest (if files are added/deleted) - Search for references to changed entities in the codebase --------- Co-authored-by: Ramimashkouk Co-authored-by: Ramimashkouk --- chatsky/__init__.py | 1 + chatsky/core/__init__.py | 22 +- chatsky/core/message.py | 8 +- chatsky/core/node_label.py | 12 +- chatsky/core/pipeline.py | 26 ++ chatsky/core/script.py | 42 ++- chatsky/core/script_parsing.py | 311 ++++++++++++++++++ chatsky/messengers/__init__.py | 9 +- chatsky/processing/__init__.py | 2 +- chatsky/processing/slots.py | 18 +- chatsky/slots/slots.py | 11 +- chatsky/utils/testing/__init__.py | 7 - docs/source/user_guides.rst | 11 +- docs/source/user_guides/pipeline_import.rst | 179 ++++++++++ poetry.lock | 4 +- pyproject.toml | 3 +- tests/core/script_parsing/__init__.py | 0 tests/core/script_parsing/custom/__init__.py | 4 + .../custom/submodule/__init__.py | 2 + .../custom/submodule/submodule/__init__.py | 2 + .../custom/submodule/submodule/file.py | 1 + .../script_parsing/custom_dir/__init__.py | 1 + .../core/script_parsing/custom_dir/module.py | 1 + tests/core/script_parsing/pipeline.json | 15 + tests/core/script_parsing/pipeline.yaml | 28 ++ .../script_parsing/test_script_parsing.py | 184 +++++++++++ tests/core/script_parsing/wrong_type.json | 4 + tests/core/test_node_label.py | 51 +++ tests/slots/test_slot_functions.py | 18 +- tests/slots/test_slot_manager.py | 34 +- tutorials/messengers/telegram/1_basic.py | 14 + tutorials/slots/1_basic_example.py | 2 - .../custom_dir/__init__.py | 0 .../custom_dir/rsp.py | 8 + .../pipeline_yaml_import_example/pipeline.py | 19 ++ .../pipeline.yaml | 112 +++++++ 36 files changed, 1100 insertions(+), 66 deletions(-) create mode 100644 chatsky/core/script_parsing.py create mode 100644 docs/source/user_guides/pipeline_import.rst create mode 100644 tests/core/script_parsing/__init__.py create mode 100644 tests/core/script_parsing/custom/__init__.py create mode 100644 tests/core/script_parsing/custom/submodule/__init__.py create mode 100644 tests/core/script_parsing/custom/submodule/submodule/__init__.py create mode 100644 tests/core/script_parsing/custom/submodule/submodule/file.py create mode 100644 tests/core/script_parsing/custom_dir/__init__.py create mode 100644 tests/core/script_parsing/custom_dir/module.py create mode 100644 tests/core/script_parsing/pipeline.json create mode 100644 tests/core/script_parsing/pipeline.yaml create mode 100644 tests/core/script_parsing/test_script_parsing.py create mode 100644 tests/core/script_parsing/wrong_type.json create mode 100644 tests/core/test_node_label.py create mode 100644 utils/pipeline_yaml_import_example/custom_dir/__init__.py create mode 100644 utils/pipeline_yaml_import_example/custom_dir/rsp.py create mode 100644 utils/pipeline_yaml_import_example/pipeline.py create mode 100644 utils/pipeline_yaml_import_example/pipeline.yaml diff --git a/chatsky/__init__.py b/chatsky/__init__.py index e1d7365f4..2aa4673b1 100644 --- a/chatsky/__init__.py +++ b/chatsky/__init__.py @@ -43,4 +43,5 @@ import chatsky.responses as rsp import chatsky.processing as proc + import chatsky.__rebuild_pydantic_models__ diff --git a/chatsky/core/__init__.py b/chatsky/core/__init__.py index 7f18e72a7..474b04c1a 100644 --- a/chatsky/core/__init__.py +++ b/chatsky/core/__init__.py @@ -3,7 +3,27 @@ """ from chatsky.core.context import Context -from chatsky.core.message import Message, MessageInitTypes +from chatsky.core.message import ( + Message, + MessageInitTypes, + Attachment, + CallbackQuery, + Location, + Contact, + Invoice, + PollOption, + Poll, + DataAttachment, + Audio, + Video, + Animation, + Image, + Sticker, + Document, + VoiceMessage, + VideoMessage, + MediaGroup, +) from chatsky.core.pipeline import Pipeline from chatsky.core.script import Node, Flow, Script from chatsky.core.script_function import BaseCondition, BaseResponse, BaseDestination, BaseProcessing, BasePriority diff --git a/chatsky/core/message.py b/chatsky/core/message.py index 006939137..24a0c7e73 100644 --- a/chatsky/core/message.py +++ b/chatsky/core/message.py @@ -6,7 +6,8 @@ It only contains types and properties that are compatible with most messaging services. """ -from typing import Literal, Optional, List, Union, Dict, Any +from __future__ import annotations +from typing import Literal, Optional, List, Union, Dict, Any, TYPE_CHECKING from typing_extensions import TypeAlias, Annotated from pathlib import Path from urllib.request import urlopen @@ -16,7 +17,6 @@ from pydantic import Field, FilePath, HttpUrl, model_validator, field_validator, field_serializer from pydantic_core import Url -from chatsky.messengers.common.interface import MessengerInterfaceWithAttachments from chatsky.utils.devel import ( json_pickle_validator, json_pickle_serializer, @@ -25,6 +25,9 @@ JSONSerializableExtras, ) +if TYPE_CHECKING: + from chatsky.messengers.common.interface import MessengerInterfaceWithAttachments + class DataModel(JSONSerializableExtras): """ @@ -283,6 +286,7 @@ class level variables to store message information. VoiceMessage, VideoMessage, MediaGroup, + DataModel, ] ] ] = None diff --git a/chatsky/core/node_label.py b/chatsky/core/node_label.py index 1e032dcb1..622cfbc21 100644 --- a/chatsky/core/node_label.py +++ b/chatsky/core/node_label.py @@ -6,7 +6,7 @@ from __future__ import annotations -from typing import Optional, Union, Tuple, TYPE_CHECKING +from typing import Optional, Union, Tuple, List, TYPE_CHECKING from typing_extensions import TypeAlias, Annotated from pydantic import BaseModel, model_validator, ValidationInfo @@ -47,7 +47,7 @@ def validate_from_str_or_tuple(cls, data, info: ValidationInfo): Allow instantiating of this class from: - A single string (node name). Also attempt to get the current flow name from context. - - A tuple of two strings (flow and node name). + - A tuple or list of two strings (flow and node name). """ if isinstance(data, str): flow_name = None @@ -55,11 +55,13 @@ def validate_from_str_or_tuple(cls, data, info: ValidationInfo): if isinstance(context, dict): flow_name = _get_current_flow_name(context.get("ctx")) return {"flow_name": flow_name, "node_name": data} - elif isinstance(data, tuple): + elif isinstance(data, (tuple, list)): if len(data) == 2 and isinstance(data[0], str) and isinstance(data[1], str): return {"flow_name": data[0], "node_name": data[1]} else: - raise ValueError(f"Cannot validate NodeLabel from {data!r}: tuple should contain 2 strings.") + raise ValueError( + f"Cannot validate NodeLabel from {data!r}: {type(data).__name__} should contain 2 strings." + ) return data @@ -67,6 +69,7 @@ def validate_from_str_or_tuple(cls, data, info: ValidationInfo): NodeLabel, Annotated[str, "node_name, flow name equal to current flow's name"], Tuple[Annotated[str, "flow_name"], Annotated[str, "node_name"]], + Annotated[List[str], "list of two strings (flow_name and node_name)"], Annotated[dict, "dict following the NodeLabel data model"], ] """Types that :py:class:`~.NodeLabel` can be validated from.""" @@ -124,6 +127,7 @@ def check_node_exists(self, info: ValidationInfo): AbsoluteNodeLabel, NodeLabel, Tuple[Annotated[str, "flow_name"], Annotated[str, "node_name"]], + Annotated[List[str], "list of two strings (flow_name and node_name)"], Annotated[dict, "dict following the AbsoluteNodeLabel data model"], ] """Types that :py:class:`~.AbsoluteNodeLabel` can be validated from.""" diff --git a/chatsky/core/pipeline.py b/chatsky/core/pipeline.py index 8c51692bf..2da7bf1dc 100644 --- a/chatsky/core/pipeline.py +++ b/chatsky/core/pipeline.py @@ -32,6 +32,7 @@ from .utils import finalize_service_group from chatsky.core.service.actor import Actor from chatsky.core.node_label import AbsoluteNodeLabel, AbsoluteNodeLabelInitTypes +from chatsky.core.script_parsing import JSONImporter, Path logger = logging.getLogger(__name__) @@ -167,6 +168,31 @@ def __init__( super().__init__(**init_dict) self.services_pipeline # cache services + @classmethod + def from_file( + cls, + file: Union[str, Path], + custom_dir: Union[str, Path] = "custom", + **overrides, + ) -> "Pipeline": + """ + Create Pipeline by importing it from a file. + A file (json or yaml) should contain a dictionary with keys being a subset of pipeline init parameters. + + See :py:meth:`.JSONImporter.import_pipeline_file` for more information. + + :param file: Path to a file containing pipeline init parameters. + :param custom_dir: Path to a directory containing custom code. + Defaults to "./custom". + If ``file`` does not use custom code, this parameter will not have any effect. + :param overrides: You can pass init parameters to override those imported from the ``file``. + """ + pipeline = JSONImporter(custom_dir=custom_dir).import_pipeline_file(file) + + pipeline.update(overrides) + + return cls(**pipeline) + @computed_field @cached_property def actor(self) -> Actor: diff --git a/chatsky/core/script.py b/chatsky/core/script.py index f70db49fe..35ca32d33 100644 --- a/chatsky/core/script.py +++ b/chatsky/core/script.py @@ -12,7 +12,7 @@ import logging from typing import List, Optional, Dict -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, AliasChoices from chatsky.core.script_function import AnyResponse, BaseProcessing from chatsky.core.node_label import AbsoluteNodeLabel @@ -21,28 +21,34 @@ logger = logging.getLogger(__name__) -class Node(BaseModel): +class Node(BaseModel, extra="forbid"): """ Node is a basic element of the dialog graph. Usually used to represent a specific state of a conversation. """ - transitions: List[Transition] = Field(default_factory=list) + transitions: List[Transition] = Field( + validation_alias=AliasChoices("transitions", "TRANSITIONS"), default_factory=list + ) """List of transitions possible from this node.""" - response: Optional[AnyResponse] = Field(default=None) + response: Optional[AnyResponse] = Field(validation_alias=AliasChoices("response", "RESPONSE"), default=None) """Response produced when this node is entered.""" - pre_transition: Dict[str, BaseProcessing] = Field(default_factory=dict) + pre_transition: Dict[str, BaseProcessing] = Field( + validation_alias=AliasChoices("pre_transition", "PRE_TRANSITION"), default_factory=dict + ) """ A dictionary of :py:class:`.BaseProcessing` functions that are executed before transitions are processed. Keys of the dictionary act as names for the processing functions. """ - pre_response: Dict[str, BaseProcessing] = Field(default_factory=dict) + pre_response: Dict[str, BaseProcessing] = Field( + validation_alias=AliasChoices("pre_response", "PRE_RESPONSE"), default_factory=dict + ) """ A dictionary of :py:class:`.BaseProcessing` functions that are executed before response is processed. Keys of the dictionary act as names for the processing functions. """ - misc: dict = Field(default_factory=dict) + misc: dict = Field(validation_alias=AliasChoices("misc", "MISC"), default_factory=dict) """ A dictionary that is used to store metadata about the node. @@ -72,7 +78,9 @@ class Flow(BaseModel, extra="allow"): This is used to group them by a specific purpose. """ - local_node: Node = Field(alias="local", default_factory=Node) + local_node: Node = Field( + validation_alias=AliasChoices("local", "LOCAL", "local_node", "LOCAL_NODE"), default_factory=Node + ) """Node from which all other nodes in this Flow inherit properties according to :py:meth:`Node.merge`.""" __pydantic_extra__: Dict[str, Node] @@ -100,7 +108,9 @@ class Script(BaseModel, extra="allow"): It represents an entire dialog graph. """ - global_node: Node = Field(alias="global", default_factory=Node) + global_node: Node = Field( + validation_alias=AliasChoices("global", "GLOBAL", "global_node", "GLOBAL_NODE"), default_factory=Node + ) """Node from which all other nodes in this Script inherit properties according to :py:meth:`Node.merge`.""" __pydantic_extra__: Dict[str, Flow] @@ -157,17 +167,17 @@ def get_inherited_node(self, label: AbsoluteNodeLabel) -> Optional[Node]: return inheritant_node.merge(self.global_node).merge(flow.local_node).merge(node) -GLOBAL = "global" +GLOBAL = "GLOBAL" """Key for :py:attr:`~chatsky.core.script.Script.global_node`.""" -LOCAL = "local" +LOCAL = "LOCAL" """Key for :py:attr:`~chatsky.core.script.Flow.local_node`.""" -TRANSITIONS = "transitions" +TRANSITIONS = "TRANSITIONS" """Key for :py:attr:`~chatsky.core.script.Node.transitions`.""" -RESPONSE = "response" +RESPONSE = "RESPONSE" """Key for :py:attr:`~chatsky.core.script.Node.response`.""" -MISC = "misc" +MISC = "MISC" """Key for :py:attr:`~chatsky.core.script.Node.misc`.""" -PRE_RESPONSE = "pre_response" +PRE_RESPONSE = "PRE_RESPONSE" """Key for :py:attr:`~chatsky.core.script.Node.pre_response`.""" -PRE_TRANSITION = "pre_transition" +PRE_TRANSITION = "PRE_TRANSITION" """Key for :py:attr:`~chatsky.core.script.Node.pre_transition`.""" diff --git a/chatsky/core/script_parsing.py b/chatsky/core/script_parsing.py new file mode 100644 index 000000000..861dce741 --- /dev/null +++ b/chatsky/core/script_parsing.py @@ -0,0 +1,311 @@ +""" +Pipeline File Import +-------------------- +This module introduces tools that allow importing Pipeline objects from +json/yaml files. + +- :py:class:`JSONImporter` is a class that imports pipeline from files +- :py:func:`get_chatsky_objects` is a function that provides an index of objects commonly used in a Pipeline definition. +""" + +from typing import Union, Optional, Any, List, Tuple +import importlib +import importlib.util +import importlib.machinery +import sys +import logging +from pathlib import Path +import json +from inspect import ismodule +from functools import reduce +from contextlib import contextmanager + +from pydantic import JsonValue + +try: + import yaml + + yaml_available = True +except ImportError: + yaml_available = False + + +logger = logging.getLogger(__name__) + + +class JSONImportError(Exception): + """An exception for incorrect usage of :py:class:`JSONImporter`.""" + + __notes__ = [ + "Read the guide on Pipeline import from file: " + "https://deeppavlov.github.io/chatsky/user_guides/pipeline_import.html" + ] + + +class JSONImporter: + """ + Enables pipeline import from file. + + Since Pipeline and all its components are already pydantic ``BaseModel``, + the only purpose of this class is to allow importing and instantiating arbitrary objects. + + Import is done by replacing strings of certain patterns with corresponding objects. + This process is implemented in :py:meth:`resolve_string_reference`. + + Instantiating is done by replacing dictionaries where a single key is an imported object + with an initialized object where arguments are specified by the dictionary values. + This process is implemented in :py:meth:`replace_resolvable_objects` and + :py:meth:`parse_args`. + + :param custom_dir: Path to the directory containing custom code available for import under the + :py:attr:`CUSTOM_DIR_NAMESPACE_PREFIX`. + """ + + CHATSKY_NAMESPACE_PREFIX: str = "chatsky." + """ + Prefix that indicates an import from the `chatsky` library. + + This class variable can be changed to allow using a different prefix. + """ + CUSTOM_DIR_NAMESPACE_PREFIX: str = "custom." + """ + Prefix that indicates an import from the custom directory. + + This class variable can be changed to allow using a different prefix. + """ + EXTERNAL_LIB_NAMESPACE_PREFIX: str = "external:" + """ + Prefix that indicates an import from any library. + + This class variable can be changed to allow using a different prefix. + """ + + def __init__(self, custom_dir: Union[str, Path]): + self.custom_dir: Path = Path(custom_dir).absolute() + self.custom_dir_location: str = str(self.custom_dir.parent) + self.custom_dir_stem: str = str(self.custom_dir.stem) + + @staticmethod + def is_resolvable(value: str) -> bool: + """ + Check if ``value`` starts with any of the namespace prefixes: + + - :py:attr:`CHATSKY_NAMESPACE_PREFIX`; + - :py:attr:`CUSTOM_DIR_NAMESPACE_PREFIX`; + - :py:attr:`EXTERNAL_LIB_NAMESPACE_PREFIX`. + + :return: Whether the value should be resolved (starts with a namespace prefix). + """ + return ( + value.startswith(JSONImporter.CHATSKY_NAMESPACE_PREFIX) + or value.startswith(JSONImporter.CUSTOM_DIR_NAMESPACE_PREFIX) + or value.startswith(JSONImporter.EXTERNAL_LIB_NAMESPACE_PREFIX) + ) + + @staticmethod + @contextmanager + def sys_path_append(path): + """ + Append ``path`` to ``sys.path`` before yielding and + restore ``sys.path`` to initial state after returning. + """ + sys_path = sys.path.copy() + sys.path.append(path) + yield + sys.path = sys_path + + @staticmethod + def replace_prefix(string, old_prefix, new_prefix) -> str: + """ + Replace ``old_prefix`` in ``string`` with ``new_prefix``. + + :raises ValueError: If the ``string`` does not begin with ``old_prefix``. + :return: A new string with a new prefix. + """ + if not string.startswith(old_prefix): + raise ValueError(f"String {string!r} does not start with {old_prefix!r}") + return new_prefix + string[len(old_prefix) :] # noqa: E203 + + def resolve_string_reference(self, obj: str) -> Any: + """ + Import an object indicated by ``obj``. + + First, ``obj`` is pre-processed -- prefixes are replaced to allow import: + + - :py:attr:`CUSTOM_DIR_NAMESPACE_PREFIX` is replaced ``{stem}.`` where `stem` is the stem of the custom dir; + - :py:attr:`CHATSKY_NAMESPACE_PREFIX` is replaced with ``chatsky.``; + - :py:attr:`EXTERNAL_LIB_NAMESPACE_PREFIX` is removed. + + Next the resulting string is imported: + If the string is ``a.b.c.d``, the following is tried in order: + + 1. ``from a import b; return b.c.d`` + 2. ``from a.b import c; return c.d`` + 3. ``from a.b.c import d; return d`` + + For custom dir imports; parent of the custom dir is appended to ``sys.path`` via :py:meth:`sys_path_append`. + + :return: An imported object. + :raises ValueError: If ``obj`` does not begin with any of the prefixes (is not :py:meth:`is_resolvable`). + :raises JSONImportError: If a string could not be imported. Includes exceptions raised on every import attempt. + """ + # prepare obj string + if obj.startswith(self.CUSTOM_DIR_NAMESPACE_PREFIX): + if not self.custom_dir.exists(): + raise JSONImportError(f"Could not find directory {self.custom_dir}") + obj = self.replace_prefix(obj, self.CUSTOM_DIR_NAMESPACE_PREFIX, self.custom_dir_stem + ".") + + elif obj.startswith(self.CHATSKY_NAMESPACE_PREFIX): + obj = self.replace_prefix(obj, self.CHATSKY_NAMESPACE_PREFIX, "chatsky.") + + elif obj.startswith(self.EXTERNAL_LIB_NAMESPACE_PREFIX): + obj = self.replace_prefix(obj, self.EXTERNAL_LIB_NAMESPACE_PREFIX, "") + + else: + raise ValueError(f"Could not find a namespace prefix: {obj}") + + # import obj + split = obj.split(".") + exceptions: List[Exception] = [] + + for module_split in range(1, len(split)): + module_name = ".".join(split[:module_split]) + object_name = split[module_split:] + try: + with self.sys_path_append(self.custom_dir_location): + module = importlib.import_module(module_name) + return reduce(getattr, [module, *object_name]) + except Exception as exc: + exceptions.append(exc) + logger.debug(f"Exception attempting to import {object_name} from {module_name!r}", exc_info=exc) + raise JSONImportError(f"Could not import {obj}") from Exception(exceptions) + + def parse_args(self, value: JsonValue) -> Tuple[list, dict]: + """ + Parse ``value`` into args and kwargs: + + - If ``value`` is a dictionary, it is returned as kwargs; + - If ``value`` is a list, it is returned as args; + - If ``value`` is ``None``, both args and kwargs are empty; + - If ``value`` is anything else, it is returned as the only arg. + + :return: A tuple of args and kwargs. + """ + args = [] + kwargs = {} + value = self.replace_resolvable_objects(value) + if isinstance(value, dict): + kwargs = value + elif isinstance(value, list): + args = value + elif value is not None: # none is used when no argument is passed: e.g. `dst.Previous:` does not accept args + args = [value] + + return args, kwargs + + def replace_resolvable_objects(self, obj: JsonValue) -> Any: + """ + Replace any resolvable objects inside ``obj`` with their resolved versions and + initialize any that are the only key of a dictionary. + + This method iterates over every value inside ``obj`` (which is ``JsonValue``). + Any string that :py:meth:`is_resolvable` is replaced with an object return from + :py:meth:`resolve_string_reference`. + This is done only once (i.e. if a string is resolved to another resolvable string, + that string is not resolved). + + Any dictionaries that contain only one resolvable key are replaced with a result of + ``resolve_string_reference(key)(*args, **kwargs)`` (the object is initialized) + where ``args`` and ``kwargs`` is the result of :py:meth:`parse_args` + on the value of the dictionary. + + :return: A new object with replaced resolvable strings and dictionaries. + """ + if isinstance(obj, dict): + keys = obj.keys() + if len(keys) == 1: + key = keys.__iter__().__next__() + if self.is_resolvable(key): + args, kwargs = self.parse_args(obj[key]) + return self.resolve_string_reference(key)(*args, **kwargs) + + return {k: (self.replace_resolvable_objects(v)) for k, v in obj.items()} + elif isinstance(obj, list): + return [self.replace_resolvable_objects(item) for item in obj] + elif isinstance(obj, str): + if self.is_resolvable(obj): + return self.resolve_string_reference(obj) + return obj + + def import_pipeline_file(self, file: Union[str, Path]) -> dict: + """ + Import a dictionary from a json/yaml file and replace resolvable objects in it. + + :return: A result of :py:meth:`replace_resolvable_objects` on the dictionary. + :raises JSONImportError: If a file does not have a correct file extension. + :raises JSONImportError: If an imported object from file is not a dictionary. + """ + file = Path(file).absolute() + + with open(file, "r", encoding="utf-8") as fd: + if file.suffix == ".json": + pipeline = json.load(fd) + elif file.suffix in (".yaml", ".yml"): + if not yaml_available: + raise ImportError("`pyyaml` package is missing.\nRun `pip install chatsky[yaml]`.") + pipeline = yaml.safe_load(fd) + else: + raise JSONImportError("File should have a `.json`, `.yaml` or `.yml` extension") + if not isinstance(pipeline, dict): + raise JSONImportError("File should contain a dict") + + logger.info(f"Loaded file {file}") + return self.replace_resolvable_objects(pipeline) + + +def get_chatsky_objects(): + """ + Return an index of most commonly used ``chatsky`` objects (in the context of pipeline initialization). + + :return: A dictionary where keys are names of the objects (e.g. ``chatsky.core.Message``) and values + are the objects. + The items in the dictionary are all the objects from the ``__init__`` files of the following modules: + + - "chatsky.cnd"; + - "chatsky.rsp"; + - "chatsky.dst"; + - "chatsky.proc"; + - "chatsky.core"; + - "chatsky.core.service"; + - "chatsky.slots"; + - "chatsky.context_storages"; + - "chatsky.messengers". + """ + json_importer = JSONImporter(custom_dir="none") + + def get_objects_from_submodule(submodule_name: str, alias: Optional[str] = None): + module = json_importer.resolve_string_reference(submodule_name) + + return { + ".".join([alias or submodule_name, name]): obj + for name, obj in module.__dict__.items() + if not name.startswith("_") and not ismodule(obj) + } + + return { + k: v + for module in ( + "chatsky.cnd", + "chatsky.rsp", + "chatsky.dst", + "chatsky.proc", + "chatsky.core", + "chatsky.core.service", + "chatsky.slots", + "chatsky.context_storages", + "chatsky.messengers", + # "chatsky.stats", + # "chatsky.utils", + ) + for k, v in get_objects_from_submodule(module).items() + } diff --git a/chatsky/messengers/__init__.py b/chatsky/messengers/__init__.py index 40a96afc6..cc979894f 100644 --- a/chatsky/messengers/__init__.py +++ b/chatsky/messengers/__init__.py @@ -1 +1,8 @@ -# -*- coding: utf-8 -*- +from chatsky.messengers.common import ( + MessengerInterface, + MessengerInterfaceWithAttachments, + PollingMessengerInterface, + CallbackMessengerInterface, +) +from chatsky.messengers.telegram import LongpollingInterface as TelegramInterface +from chatsky.messengers.console import CLIMessengerInterface diff --git a/chatsky/processing/__init__.py b/chatsky/processing/__init__.py index 4bc71cf5d..fcd984a05 100644 --- a/chatsky/processing/__init__.py +++ b/chatsky/processing/__init__.py @@ -1,2 +1,2 @@ from .standard import ModifyResponse -from .slots import Extract, ExtractAll, Unset, UnsetAll, FillTemplate +from .slots import Extract, Unset, UnsetAll, FillTemplate diff --git a/chatsky/processing/slots.py b/chatsky/processing/slots.py index 6bf017782..898195e94 100644 --- a/chatsky/processing/slots.py +++ b/chatsky/processing/slots.py @@ -24,14 +24,16 @@ class Extract(BaseProcessing): slots: List[SlotName] """A list of slot names to extract.""" + success_only: bool = True + """If set, only successfully extracted values will be stored in the slot storage.""" - def __init__(self, *slots: SlotName): - super().__init__(slots=slots) + def __init__(self, *slots: SlotName, success_only: bool = True): + super().__init__(slots=slots, success_only=success_only) async def call(self, ctx: Context): manager = ctx.framework_data.slot_manager results = await asyncio.gather( - *(manager.extract_slot(slot, ctx) for slot in self.slots), return_exceptions=True + *(manager.extract_slot(slot, ctx, self.success_only) for slot in self.slots), return_exceptions=True ) for result in results: @@ -39,16 +41,6 @@ async def call(self, ctx: Context): logger.exception("An exception occurred during slot extraction.", exc_info=result) -class ExtractAll(BaseProcessing): - """ - Extract all slots defined in the pipeline. - """ - - async def call(self, ctx: Context): - manager = ctx.framework_data.slot_manager - await manager.extract_all(ctx) - - class Unset(BaseProcessing): """ Mark specified slots as not extracted and clear extracted values. diff --git a/chatsky/slots/slots.py b/chatsky/slots/slots.py index a6981fabc..276a28f56 100644 --- a/chatsky/slots/slots.py +++ b/chatsky/slots/slots.py @@ -239,6 +239,8 @@ async def get_value(self, ctx: Context) -> ExtractedValueSlot: logger.exception(f"Exception occurred during {self.__class__.__name__!r} extraction.", exc_info=error) extracted_value = error finally: + if not is_slot_extracted: + logger.debug(f"Slot {self.__class__.__name__!r} was not extracted: {extracted_value}") return ExtractedValueSlot.model_construct( is_slot_extracted=is_slot_extracted, extracted_value=extracted_value, @@ -362,16 +364,21 @@ def get_slot(self, slot_name: SlotName) -> BaseSlot: return slot raise KeyError(f"Could not find slot {slot_name!r}.") - async def extract_slot(self, slot_name: SlotName, ctx: Context) -> None: + async def extract_slot(self, slot_name: SlotName, ctx: Context, success_only: bool) -> None: """ Extract slot `slot_name` and store extracted value in `slot_storage`. :raises KeyError: If the slot with the specified name does not exist. + + :param slot_name: Name of the slot to extract. + :param ctx: Context. + :param success_only: Whether to store the value only if it is successfully extracted. """ slot = self.get_slot(slot_name) value = await slot.get_value(ctx) - recursive_setattr(self.slot_storage, slot_name, value) + if value.__slot_extracted__ or success_only is False: + recursive_setattr(self.slot_storage, slot_name, value) async def extract_all(self, ctx: Context): """ diff --git a/chatsky/utils/testing/__init__.py b/chatsky/utils/testing/__init__.py index bfbe04fef..334d4d077 100644 --- a/chatsky/utils/testing/__init__.py +++ b/chatsky/utils/testing/__init__.py @@ -1,10 +1,3 @@ # -*- coding: utf-8 -*- from .common import is_interactive_mode, check_happy_path from .toy_script import TOY_SCRIPT, TOY_SCRIPT_KWARGS, HAPPY_PATH - -try: - import pytest - - pytest.register_assert_rewrite("chatsky.utils.testing.telegram") -except ImportError: - ... diff --git a/docs/source/user_guides.rst b/docs/source/user_guides.rst index 6802a4b34..b8dbc376d 100644 --- a/docs/source/user_guides.rst +++ b/docs/source/user_guides.rst @@ -4,7 +4,7 @@ User guides :doc:`Basic concepts <./user_guides/basic_conceptions>` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In the ``basic concepts`` tutorial the basics of Chatsky are described, +In the ``basic concepts`` guide the basics of Chatsky are described, those include but are not limited to: dialog graph creation, specifying start and fallback nodes, setting transitions and conditions, using ``Context`` object in order to receive information about current script execution. @@ -26,7 +26,7 @@ The ``context guide`` walks you through the details of working with the :doc:`Superset guide <./user_guides/superset_guide>` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``superset guide`` tutorial highlights the usage of Superset visualization tool +The ``superset guide`` highlights the usage of Superset visualization tool for exploring the telemetry data collected from your conversational services. We show how to plug in the telemetry collection and configure the pre-built Superset dashboard shipped with Chatsky. @@ -38,6 +38,12 @@ The ``optimization guide`` demonstrates various tools provided by the library that you can use to profile your conversational service, and to locate and remove performance bottlenecks. +:doc:`YAML import guide <./user_guides/pipeline_import>` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``yaml import guide`` shows another option for initializing ``Pipeline`` +objects -- from yaml or json files. + .. toctree:: :hidden: @@ -46,3 +52,4 @@ and to locate and remove performance bottlenecks. user_guides/context_guide user_guides/superset_guide user_guides/optimization_guide + user_guides/pipeline_import diff --git a/docs/source/user_guides/pipeline_import.rst b/docs/source/user_guides/pipeline_import.rst new file mode 100644 index 000000000..c50b56259 --- /dev/null +++ b/docs/source/user_guides/pipeline_import.rst @@ -0,0 +1,179 @@ +Pipeline YAML import guide +-------------------------- + +Introduction +~~~~~~~~~~~~ + +Instead of passing all the arguments to pipeline from a python environment, +you can initialize pipeline by getting the arguments from a file. + +The details of this process are described in this guide. + +Basics +~~~~~~ + +To initialize ``Pipeline`` from a file, call its `from_file <../apiref/chatsky.core.pipeline.html#chatsky.core.pipeline.Pipeline.from_file>`_ +method. It accepts a path to a file, a path to a custom code directory and overrides. + +File +==== + +The file should be a json or yaml file that contains a dictionary. +They keys in the dictionary are the names of pipeline init parameters and the values are the values of the parameters. + +Below is a minimalistic example of such a file: + +.. code-block:: yaml + + script: + flow: + node: + RESPONSE: Hi + TRANSITIONS: + - dst: node + cnd: true + priority: 2 + start_label: + - flow + - node + +.. note:: + + If you're using yaml files, you need to install pyyaml: + + .. code-block:: sh + + pip install chatsky[yaml] + + +Custom dir +========== + +Custom directory allows using any objects inside the yaml file. + +More on that in the :ref:`object-import` section. + +Overrides +========= + +Any pipeline init parameters can be passed to ``from_file``. +They will override parameters defined in the file (or add them if they are not defined in the file). + +.. _object-import: + +Object Import +~~~~~~~~~~~~~ + +JSON values are often not enough to build any serious script. + +For this reason, the init parameters in the pipeline file are preprocessed in two ways: + +String reference replacement +============================ + +Any string that begins with either ``chatsky.``, ``custom.`` or ``external:`` is replaced with a corresponding object. + +The ``chatsky.`` prefix indicates that an object should be found inside the ``chatsky`` library. +For example, string ``chatsky.cnd.ExactMatch`` will be replaced with the ``chatsky.cnd.ExactMatch`` object (which is a class). + +The ``custom.`` prefix allows importing object from the custom directory passed to ``Pipeline.from_file``. +For example, string ``custom.my_response`` will be replaced with the ``my_response`` object defined in ``custom/__init__.py`` +(or will throw an exception if there's no such object). + +The ``external:`` prefix can be used to import any objects (primarily, from external libraries). +For example, string ``external:os.getenv`` will be replaced with the function ``os.getenv``. + +.. note:: + + It is highly recommended to read about the import process for these strings + `here <../apiref/chatsky.core.script_parsing.html#chatsky.core.script_parsing.JSONImporter.resolve_string_reference>`_. + +.. note:: + + If you want to use different prefixes, you can edit the corresponding class variables of the + `JSONImporter <../apiref/chatsky.core.script_parsing.html#chatsky.core.script_parsing.JSONImporter>`_ class: + + .. code-block:: python + + from chatsky.core.script_parsing import JSONImporter + from chatsky import Pipeline + + JSONImporter.CHATSKY_NAMESPACE_PREFIX = "_chatsky:" + + pipeline = Pipeline.from_file(...) + + After changing the prefix variable, ``from_file`` will no longer replace strings that start with ``chatsky.``. + (and will replace strings that start with ``_chatsky:``) + +Single-key dict replacement +=========================== + +Any dictionary containing a **single** key that **begins with any of the prefixes** described in the previous section +will be replaced with a call result of the object referenced by the key. + +Call is made with the arguments passed as a value of the dictionary: + +- If the value is a dictionary; it is passed as kwargs; +- If the value is a list; it is passed as args; +- If the value is ``None``; no arguments are passed; +- Otherwise, the value is passed as the only arg. + +.. list-table:: Examples + :widths: auto + :header-rows: 1 + + * - YAML string + - Resulting object + - Note + * - .. code-block:: yaml + + external:os.getenv: TOKEN + - .. code-block:: python + + os.getenv("TOKEN") + - This falls into the 4th condition (value is not a dict, list or None) so it is passed as the only argument. + * - .. code-block:: yaml + + chatsky.dst.Previous: + - .. code-block:: python + + chatsky.dst.Previous() + - The value is ``None``, so there are no arguments. + * - .. code-block:: yaml + + chatsky.dst.Previous + - .. code-block:: python + + chatsky.dst.Previous + - This is not a dictionary, the resulting object is a class! + * - .. code-block:: yaml + + chatsky.cnd.Regexp: + pattern: "yes" + flags: external:re.I + - .. code-block:: python + + chatsky.cnd.Regexp( + pattern="yes", + flags=re.I + ) + - The value is a dictionary; it is passed as kwargs. + This also showcases that replacement is recursive ``external:re.I`` is replaced as well. + * - .. code-block:: yaml + + chatsky.proc.Extract: + - person.name + - person.age + - .. code-block:: python + + chatsky.proc.Extract( + "person.name", + "person.age" + ) + - The value is a list; it is passed as args. + +Further reading +~~~~~~~~~~~~~~~ + +* `API ref <../apiref/chatsky.core.script_parsing.html>`_ +* `Comprehensive example `_ diff --git a/poetry.lock b/poetry.lock index 2f12d0b3e..ad192e6a2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4952,7 +4952,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -6936,9 +6935,10 @@ redis = ["redis"] sqlite = ["aiosqlite", "sqlalchemy"] stats = ["omegaconf", "opentelemetry-exporter-otlp", "opentelemetry-instrumentation", "requests", "tqdm"] telegram = ["python-telegram-bot"] +yaml = ["pyyaml"] ydb = ["six", "ydb"] [metadata] lock-version = "2.0" python-versions = "^3.8.1,!=3.9.7" -content-hash = "a4e53a8b58504d6e4f877ac5e7901d5aa8451003bf9edf55ebfb4df7af8424ab" +content-hash = "511348f67731d8a26e0a269d3f8f032368a85289cdd4772df378335c57812201" diff --git a/pyproject.toml b/pyproject.toml index cbde2143f..a6ff0a967 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ python-telegram-bot = { version = "~=21.3", extras = ["all"], optional = true } opentelemetry-instrumentation = { version = "*", optional = true } sqlalchemy = { version = "*", extras = ["asyncio"], optional = true } opentelemetry-exporter-otlp = { version = ">=1.20.0", optional = true } # log body serialization is required +pyyaml = { version = "*", optional = true } [tool.poetry.extras] json = ["aiofiles"] @@ -87,6 +88,7 @@ ydb = ["ydb", "six"] telegram = ["python-telegram-bot"] stats = ["opentelemetry-exporter-otlp", "opentelemetry-instrumentation", "requests", "tqdm", "omegaconf"] benchmark = ["pympler", "humanize", "pandas", "altair", "tqdm"] +yaml = ["pyyaml"] [tool.poetry.group.lint] @@ -224,5 +226,4 @@ concurrency = [ exclude_also = [ "if TYPE_CHECKING:", "raise NotImplementedError", - "raise RuntimeError", ] diff --git a/tests/core/script_parsing/__init__.py b/tests/core/script_parsing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/script_parsing/custom/__init__.py b/tests/core/script_parsing/custom/__init__.py new file mode 100644 index 000000000..daa9575c6 --- /dev/null +++ b/tests/core/script_parsing/custom/__init__.py @@ -0,0 +1,4 @@ +from . import submodule as sub +from .submodule import VAR as V + +recurse = "custom.V" diff --git a/tests/core/script_parsing/custom/submodule/__init__.py b/tests/core/script_parsing/custom/submodule/__init__.py new file mode 100644 index 000000000..1e4d22038 --- /dev/null +++ b/tests/core/script_parsing/custom/submodule/__init__.py @@ -0,0 +1,2 @@ +from . import submodule as sub +from .submodule import V as VAR diff --git a/tests/core/script_parsing/custom/submodule/submodule/__init__.py b/tests/core/script_parsing/custom/submodule/submodule/__init__.py new file mode 100644 index 000000000..f59f611ce --- /dev/null +++ b/tests/core/script_parsing/custom/submodule/submodule/__init__.py @@ -0,0 +1,2 @@ +from . import file as f +from .file import VAR as V diff --git a/tests/core/script_parsing/custom/submodule/submodule/file.py b/tests/core/script_parsing/custom/submodule/submodule/file.py new file mode 100644 index 000000000..7a4283691 --- /dev/null +++ b/tests/core/script_parsing/custom/submodule/submodule/file.py @@ -0,0 +1 @@ +VAR = 1 diff --git a/tests/core/script_parsing/custom_dir/__init__.py b/tests/core/script_parsing/custom_dir/__init__.py new file mode 100644 index 000000000..f46b7b8d2 --- /dev/null +++ b/tests/core/script_parsing/custom_dir/__init__.py @@ -0,0 +1 @@ +VAR = 2 diff --git a/tests/core/script_parsing/custom_dir/module.py b/tests/core/script_parsing/custom_dir/module.py new file mode 100644 index 000000000..ec8099c45 --- /dev/null +++ b/tests/core/script_parsing/custom_dir/module.py @@ -0,0 +1 @@ +VAR = 3 diff --git a/tests/core/script_parsing/pipeline.json b/tests/core/script_parsing/pipeline.json new file mode 100644 index 000000000..359643f43 --- /dev/null +++ b/tests/core/script_parsing/pipeline.json @@ -0,0 +1,15 @@ +{ + "script": { + "flow": { + "node": { + "misc": { + "key": "custom.V" + } + } + } + }, + "start_label": [ + "flow", + "node" + ] +} \ No newline at end of file diff --git a/tests/core/script_parsing/pipeline.yaml b/tests/core/script_parsing/pipeline.yaml new file mode 100644 index 000000000..5b26e179e --- /dev/null +++ b/tests/core/script_parsing/pipeline.yaml @@ -0,0 +1,28 @@ +script: + flow: + node: + response: + text: hi + misc: + key: custom.V + transitions: + - dst: + chatsky.dst.Previous: + cnd: + chatsky.cnd.HasText: t +start_label: + - flow + - node +fallback_label: + - other_flow + - other_node +slots: + person: + likes: + chatsky.slots.RegexpSlot: + regexp: "I like (.+)" + match_group_idx: 1 + age: + chatsky.slots.RegexpSlot: + regexp: "I'm ([0-9]+) years old" + match_group_idx: 1 diff --git a/tests/core/script_parsing/test_script_parsing.py b/tests/core/script_parsing/test_script_parsing.py new file mode 100644 index 000000000..9307f4f15 --- /dev/null +++ b/tests/core/script_parsing/test_script_parsing.py @@ -0,0 +1,184 @@ +from pathlib import Path + +import pytest + +import chatsky +from chatsky.core.script_parsing import JSONImporter, JSONImportError, get_chatsky_objects, yaml_available + + +current_dir = Path(__file__).parent.absolute() + + +class TestResolveStringReference: + @pytest.mark.parametrize( + "string", + [ + "custom.V", + "custom.sub.VAR", + "custom.sub.sub.V", + "custom.sub.sub.f.VAR", + "custom.submodule.VAR", + "custom.submodule.sub.V", + "custom.submodule.sub.f.VAR", + "custom.submodule.submodule.V", + "custom.submodule.submodule.f.VAR", + "custom.submodule.submodule.file.VAR", + "custom.sub.submodule.V", + "custom.sub.submodule.f.VAR", + "custom.sub.submodule.file.VAR", + ], + ) + def test_resolve_custom_object(self, string): + json_importer = JSONImporter(custom_dir=current_dir / "custom") + + assert json_importer.resolve_string_reference(string) == 1 + + def test_different_custom_location(self): + json_importer = JSONImporter(custom_dir=current_dir / "custom_dir") + + assert json_importer.resolve_string_reference("custom.VAR") == 2 + assert json_importer.resolve_string_reference("custom.module.VAR") == 3 + + @pytest.mark.parametrize( + "obj,val", + [ + ("chatsky.cnd.ExactMatch", chatsky.conditions.ExactMatch), + ("chatsky.conditions.standard.ExactMatch", chatsky.conditions.ExactMatch), + ("chatsky.core.message.Image", chatsky.core.message.Image), + ("chatsky.Message", chatsky.Message), + ("chatsky.context_storages.sql.SQLContextStorage", chatsky.context_storages.sql.SQLContextStorage), + ("chatsky.messengers.telegram.LongpollingInterface", chatsky.messengers.telegram.LongpollingInterface), + ("chatsky.LOCAL", "LOCAL"), + ], + ) + def test_resolve_chatsky_objects(self, obj, val): + json_importer = JSONImporter(custom_dir=current_dir / "none") + + assert json_importer.resolve_string_reference(obj) == val + + def test_resolve_external_objects(self): + json_importer = JSONImporter(custom_dir=current_dir / "none") + + assert json_importer.resolve_string_reference("external:logging.DEBUG") == 10 + + def test_alternative_domain_names(self, monkeypatch): + monkeypatch.setattr(JSONImporter, "CHATSKY_NAMESPACE_PREFIX", "_chatsky:") + monkeypatch.setattr(JSONImporter, "CUSTOM_DIR_NAMESPACE_PREFIX", "_custom:") + + json_importer = JSONImporter(custom_dir=current_dir / "custom") + + assert json_importer.resolve_string_reference("_chatsky:Message") == chatsky.Message + assert json_importer.resolve_string_reference("_custom:V") == 1 + + def test_non_existent_custom_dir(self): + json_importer = JSONImporter(custom_dir=current_dir / "none") + with pytest.raises(JSONImportError, match="Could not find directory"): + json_importer.resolve_string_reference("custom.VAR") + + def test_wrong_prefix(self): + json_importer = JSONImporter(custom_dir=current_dir / "none") + with pytest.raises(ValueError, match="prefix"): + json_importer.resolve_string_reference("wrong_domain.VAR") + + def test_non_existent_object(self): + json_importer = JSONImporter(custom_dir=current_dir / "custom_dir") + with pytest.raises(JSONImportError, match="Could not import"): + json_importer.resolve_string_reference("chatsky.none.VAR") + with pytest.raises(JSONImportError, match="Could not import"): + json_importer.resolve_string_reference("custom.none.VAR") + + +@pytest.mark.parametrize( + "obj,replaced", + [ + (5, 5), + (True, True), + ("string", "string"), + ("custom.V", 1), + ("chatsky.LOCAL", "LOCAL"), + ({"text": "custom.V"}, {"text": 1}), + ({"1": {"2": "custom.V"}}, {"1": {"2": 1}}), + ({"1": "custom.V", "2": "custom.V"}, {"1": 1, "2": 1}), + (["custom.V", 4], [1, 4]), + ({"chatsky.Message": None}, chatsky.Message()), + ({"chatsky.Message": ["text"]}, chatsky.Message("text")), + ({"chatsky.Message": {"text": "text", "misc": {}}}, chatsky.Message("text", misc={})), + ({"chatsky.Message": ["chatsky.LOCAL"]}, chatsky.Message("LOCAL")), + ({"chatsky.Message": {"text": "LOCAL"}}, chatsky.Message("LOCAL")), + ], +) +def test_replace_resolvable_objects(obj, replaced): + json_importer = JSONImporter(custom_dir=current_dir / "custom") + + assert json_importer.replace_resolvable_objects(obj) == replaced + + +def test_nested_replacement(): + json_importer = JSONImporter(custom_dir=current_dir / "none") + + obj = json_importer.replace_resolvable_objects({"chatsky.cnd.Negation": {"chatsky.cnd.HasText": {"text": "text"}}}) + + assert isinstance(obj, chatsky.cnd.Negation) + assert isinstance(obj.condition, chatsky.cnd.HasText) + assert obj.condition.text == "text" + + +def test_no_recursion(): + json_importer = JSONImporter(custom_dir=current_dir / "custom") + + obj = json_importer.replace_resolvable_objects( + {"chatsky.cnd.Negation": {"chatsky.cnd.HasText": {"text": "custom.recurse"}}} + ) + + assert obj.condition.text == "custom.V" + + +class TestImportPipelineFile: + @pytest.mark.skipif(not yaml_available, reason="YAML dependencies missing") + def test_normal_import(self): + pipeline = chatsky.Pipeline.from_file( + current_dir / "pipeline.yaml", + custom_dir=current_dir / "custom", + fallback_label=("flow", "node"), # override the parameter + ) + + assert pipeline.start_label.node_name == "node" + assert pipeline.fallback_label.node_name == "node" + start_node = pipeline.script.get_node(pipeline.start_label) + assert start_node.response.root == chatsky.Message("hi", misc={"key": 1}) + assert start_node.transitions[0].dst == chatsky.dst.Previous() + assert start_node.transitions[0].cnd == chatsky.cnd.HasText("t") + + assert pipeline.slots.person.likes == chatsky.slots.RegexpSlot(regexp="I like (.+)", match_group_idx=1) + assert pipeline.slots.person.age == chatsky.slots.RegexpSlot(regexp="I'm ([0-9]+) years old", match_group_idx=1) + + def test_import_json(self): + pipeline = chatsky.Pipeline.from_file(current_dir / "pipeline.json", custom_dir=current_dir / "custom") + + assert pipeline.script.get_node(pipeline.start_label).misc == {"key": 1} + + def test_wrong_file_ext(self): + with pytest.raises(JSONImportError, match="extension"): + chatsky.Pipeline.from_file(current_dir / "__init__.py") + + def test_wrong_object_type(self): + with pytest.raises(JSONImportError, match="dict"): + chatsky.Pipeline.from_file(current_dir / "wrong_type.json") + + +@pytest.mark.parametrize( + "key,value", + [ + ("chatsky.cnd.ExactMatch", chatsky.conditions.ExactMatch), + ("chatsky.core.Image", chatsky.core.message.Image), + ("chatsky.core.Message", chatsky.Message), + ("chatsky.context_storages.SQLContextStorage", chatsky.context_storages.sql.SQLContextStorage), + ("chatsky.messengers.TelegramInterface", chatsky.messengers.telegram.LongpollingInterface), + ("chatsky.slots.RegexpSlot", chatsky.slots.RegexpSlot), + ], +) +def test_get_chatsky_objects(key, value): + json_importer = JSONImporter(custom_dir=current_dir / "none") + + assert json_importer.resolve_string_reference(key) == value + assert get_chatsky_objects()[key] == value diff --git a/tests/core/script_parsing/wrong_type.json b/tests/core/script_parsing/wrong_type.json new file mode 100644 index 000000000..63964002b --- /dev/null +++ b/tests/core/script_parsing/wrong_type.json @@ -0,0 +1,4 @@ +[ + 1, + 2 +] \ No newline at end of file diff --git a/tests/core/test_node_label.py b/tests/core/test_node_label.py new file mode 100644 index 000000000..8580f5f5f --- /dev/null +++ b/tests/core/test_node_label.py @@ -0,0 +1,51 @@ +import pytest +from pydantic import ValidationError + +from chatsky.core import NodeLabel, Context, AbsoluteNodeLabel, Pipeline + + +def test_init_from_single_string(): + ctx = Context.init(("flow", "node1")) + ctx.framework_data.pipeline = Pipeline({"flow": {"node2": {}}}, ("flow", "node2")) + + node = AbsoluteNodeLabel.model_validate("node2", context={"ctx": ctx}) + + assert node == AbsoluteNodeLabel(flow_name="flow", node_name="node2") + + +@pytest.mark.parametrize("data", [("flow", "node"), ["flow", "node"]]) +def test_init_from_iterable(data): + node = AbsoluteNodeLabel.model_validate(data) + assert node == AbsoluteNodeLabel(flow_name="flow", node_name="node") + + +@pytest.mark.parametrize( + "data,msg", + [ + (["flow", "node", 3], "list should contain 2 strings"), + ((1, 2), "tuple should contain 2 strings"), + ], +) +def test_init_from_incorrect_iterables(data, msg): + with pytest.raises(ValidationError, match=msg): + AbsoluteNodeLabel.model_validate(data) + + +def test_init_from_node_label(): + with pytest.raises(ValidationError): + AbsoluteNodeLabel.model_validate(NodeLabel(node_name="node")) + + ctx = Context.init(("flow", "node1")) + ctx.framework_data.pipeline = Pipeline({"flow": {"node2": {}}}, ("flow", "node2")) + + node = AbsoluteNodeLabel.model_validate(NodeLabel(node_name="node2"), context={"ctx": ctx}) + + assert node == AbsoluteNodeLabel(flow_name="flow", node_name="node2") + + +def test_check_node_exists(): + ctx = Context.init(("flow", "node1")) + ctx.framework_data.pipeline = Pipeline({"flow": {"node2": {}}}, ("flow", "node2")) + + with pytest.raises(ValidationError, match="Cannot find node"): + AbsoluteNodeLabel.model_validate(("flow", "node3"), context={"ctx": ctx}) diff --git a/tests/slots/test_slot_functions.py b/tests/slots/test_slot_functions.py index b0c80a651..83c6b0171 100644 --- a/tests/slots/test_slot_functions.py +++ b/tests/slots/test_slot_functions.py @@ -56,10 +56,16 @@ def func(*args, **kwargs): async def test_basic_functions(context, manager, log_event_catcher): + await proc.Extract("0", "2", "err").wrapped_call(context) + + assert manager.get_extracted_slot("0").value == 4 + assert manager.is_slot_extracted("1") is False + assert isinstance(manager.get_extracted_slot("err").extracted_value, SlotNotExtracted) + proc_logs = log_event_catcher(proc_logger, level=logging.ERROR) slot_logs = log_event_catcher(slot_logger, level=logging.ERROR) - await proc.Extract("0", "2", "err").wrapped_call(context) + await proc.Extract("0", "2", "err", success_only=False).wrapped_call(context) assert manager.get_extracted_slot("0").value == 4 assert manager.is_slot_extracted("1") is False @@ -82,16 +88,6 @@ async def test_basic_functions(context, manager, log_event_catcher): assert await cnd.SlotsExtracted("0", "1", mode="any").wrapped_call(context) is False -async def test_extract_all(context, manager, monkeypatch, call_logger_factory): - logs, func = call_logger_factory() - - monkeypatch.setattr(SlotManager, "extract_all", func) - - await proc.ExtractAll().wrapped_call(context) - - assert logs == [{"args": (manager, context), "kwargs": {}}] - - async def test_unset_all(context, manager, monkeypatch, call_logger_factory): logs, func = call_logger_factory() diff --git a/tests/slots/test_slot_manager.py b/tests/slots/test_slot_manager.py index b6b3f6db1..33885b360 100644 --- a/tests/slots/test_slot_manager.py +++ b/tests/slots/test_slot_manager.py @@ -174,7 +174,39 @@ def test_get_slot_by_name(empty_slot_manager): ], ) async def test_slot_extraction(slot_name, expected_slot_storage, empty_slot_manager, context_with_request): - await empty_slot_manager.extract_slot(slot_name, context_with_request) + await empty_slot_manager.extract_slot(slot_name, context_with_request, success_only=False) + assert empty_slot_manager.slot_storage == expected_slot_storage + + +@pytest.mark.parametrize( + "slot_name,expected_slot_storage", + [ + ( + "person.name", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=extracted_slot_values["person.name"], + surname=init_value_slot, + email=init_value_slot, + ), + msg_len=init_value_slot, + ), + ), + ( + "person.surname", + ExtractedGroupSlot( + person=ExtractedGroupSlot( + name=init_value_slot, + surname=init_value_slot, + email=init_value_slot, + ), + msg_len=init_value_slot, + ), + ), + ], +) +async def test_successful_extraction(slot_name, expected_slot_storage, empty_slot_manager, context_with_request): + await empty_slot_manager.extract_slot(slot_name, context_with_request, success_only=True) assert empty_slot_manager.slot_storage == expected_slot_storage diff --git a/tutorials/messengers/telegram/1_basic.py b/tutorials/messengers/telegram/1_basic.py index dec0ea10e..66dd055f1 100644 --- a/tutorials/messengers/telegram/1_basic.py +++ b/tutorials/messengers/telegram/1_basic.py @@ -48,6 +48,20 @@ class and [python-telegram-bot](https://docs.python-telegram-bot.org/) Either of the two interfaces connect the bot to Telegram. They can be passed directly to a Chatsky `Pipeline` instance. + + +
+ +Note + +You can also import `LongpollingInterface` +under the alias of `TelegramInterface` from `chatsky.messengers`: + +```python +from chatsky.messengers import TelegramInterface +``` + +
""" diff --git a/tutorials/slots/1_basic_example.py b/tutorials/slots/1_basic_example.py index 66e8cedcb..dcddfbade 100644 --- a/tutorials/slots/1_basic_example.py +++ b/tutorials/slots/1_basic_example.py @@ -78,8 +78,6 @@ Condition for checking if specified slots are extracted. - %mddoclink(api,processing.slots,Extract): A processing function that extracts specified slots. -- %mddoclink(api,processing.slots,ExtractAll): - A processing function that extracts all slots. - %mddoclink(api,processing.slots,Unset): A processing function that marks specified slots as not extracted, effectively resetting their state. diff --git a/utils/pipeline_yaml_import_example/custom_dir/__init__.py b/utils/pipeline_yaml_import_example/custom_dir/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/pipeline_yaml_import_example/custom_dir/rsp.py b/utils/pipeline_yaml_import_example/custom_dir/rsp.py new file mode 100644 index 000000000..b937639e8 --- /dev/null +++ b/utils/pipeline_yaml_import_example/custom_dir/rsp.py @@ -0,0 +1,8 @@ +from chatsky import BaseResponse, Context, MessageInitTypes, cnd + + +class ListNotExtractedSlots(BaseResponse): + async def call(self, ctx: Context) -> MessageInitTypes: + not_extracted_slots = [key for key in ("name", "age") if not await cnd.SlotsExtracted(f"person.{key}")(ctx)] + + return f"You did not provide {not_extracted_slots} yet." diff --git a/utils/pipeline_yaml_import_example/pipeline.py b/utils/pipeline_yaml_import_example/pipeline.py new file mode 100644 index 000000000..970ab60db --- /dev/null +++ b/utils/pipeline_yaml_import_example/pipeline.py @@ -0,0 +1,19 @@ +from pathlib import Path +import logging + +from chatsky import Pipeline + + +logging.basicConfig(level=logging.INFO) + +current_dir = Path(__file__).parent + +pipeline = Pipeline.from_file( + file=current_dir / "pipeline.yaml", + custom_dir=current_dir / "custom_dir", + # these paths can also be relative (e.g. file="pipeline.yaml") + # but that would only work if executing pipeline in this directory +) + +if __name__ == "__main__": + pipeline.run() diff --git a/utils/pipeline_yaml_import_example/pipeline.yaml b/utils/pipeline_yaml_import_example/pipeline.yaml new file mode 100644 index 000000000..b7c638c45 --- /dev/null +++ b/utils/pipeline_yaml_import_example/pipeline.yaml @@ -0,0 +1,112 @@ +script: + GLOBAL: + TRANSITIONS: + - dst: [tech_flow, start_node] + cnd: + chatsky.cnd.ExactMatch: /start + priority: 2 + tech_flow: + start_node: + RESPONSE: + text: + "Hello. + We'd like to collect some data about you. + Do you agree? (yes/no)" + PRE_TRANSITION: + unset_all_slots: + chatsky.proc.UnsetAll: + TRANSITIONS: + - dst: [data_collection, start] + cnd: + chatsky.cnd.Regexp: + pattern: "yes" + flags: external:re.IGNORECASE + fallback_node: + RESPONSE: + "Dialog finished. + You can restart by typing /start." + data_collection: + LOCAL: + PRE_TRANSITION: + extract_slots: + chatsky.proc.Extract: + - person.name + - person.age + TRANSITIONS: + - dst: not_provided_slots + cnd: + chatsky.cnd.Negation: + chatsky.cnd.SlotsExtracted: + - person.name + - person.age + priority: 0.5 + - dst: name_extracted + cnd: + chatsky.cnd.All: + - chatsky.cnd.HasText: My name + - chatsky.cnd.SlotsExtracted: person.name + - dst: age_extracted + cnd: + chatsky.cnd.All: + - chatsky.cnd.HasText: years old + - chatsky.cnd.SlotsExtracted: person.age + - dst: [final_flow, all_slots_extracted] + cnd: + chatsky.cnd.SlotsExtracted: + - person.name + - person.age + priority: 1.5 + start: + RESPONSE: + text: + "Please provide us with the following data: + + Your *name* by sending message \"My name is X\" + + Your *age* by sending message \"I'm X years old\"" + parse_mode: external:telegram.constants.ParseMode.MARKDOWN_V2 + not_provided_slots: + RESPONSE: + custom.rsp.ListNotExtractedSlots: + name_extracted: + RESPONSE: + Got your name. Now provide your age. + age_extracted: + RESPONSE: + Got your age. Now provide your name. + final_flow: + all_slots_extracted: + RESPONSE: + chatsky.rsp.FilledTemplate: + chatsky.Message: + text: + "Thank you for providing us your data. + + Your name: {person.name}; + Your age: {person.age}. + + Here's a cute sticker as a reward:" + attachments: + - chatsky.core.Sticker: + id: CAACAgIAAxkBAAErBZ1mKAbZvEOmhscojaIL5q0u8vgp1wACRygAAiSjCUtLa7RHZy76ezQE +start_label: + - tech_flow + - start_node +fallback_label: + - tech_flow + - fallback_node +slots: + person: + name: + chatsky.slots.RegexpSlot: + regexp: "My name is (.+)" + match_group_idx: 1 + age: + chatsky.slots.RegexpSlot: + regexp: "I'm ([0-9]+) years old" + match_group_idx: 1 +messenger_interface: + chatsky.messengers.TelegramInterface: + token: + external:os.getenv: + TG_BOT_TOKEN From a7df04a34c2dfde88e2220d29d904ab892c8d3bb Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Sun, 8 Sep 2024 00:14:33 +0300 Subject: [PATCH 07/11] fix node merge (#387) # Description Now dictionaries are merged in a way that localized processing functions are called first. (node > local > global) # Checklist - [x] I have performed a self-review of the changes *List here tasks to complete in order to mark this PR as ready for review.* # To Consider - Add tests (if functionality is changed) - Update API reference / tutorials / guides - Update CONTRIBUTING.md (if devel workflow is changed) - Update `.ignore` files, scripts (such as `lint`), distribution manifest (if files are added/deleted) - Search for references to changed entities in the codebase --- chatsky/core/script.py | 52 ++++++---- tests/core/test_script.py | 95 +++++++++++-------- .../script/core/7_pre_response_processing.py | 30 +++++- tutorials/script/core/8_misc.py | 16 ++-- 4 files changed, 124 insertions(+), 69 deletions(-) diff --git a/chatsky/core/script.py b/chatsky/core/script.py index 35ca32d33..69ab692b6 100644 --- a/chatsky/core/script.py +++ b/chatsky/core/script.py @@ -55,20 +55,28 @@ class Node(BaseModel, extra="forbid"): Can be accessed at runtime via :py:attr:`~chatsky.core.context.Context.current_node`. """ - def merge(self, other: Node): + def inherit_from_other(self, other: Node): """ - Merge another node into this one: + Inherit properties from another node into this one: - - Prepend :py:attr:`transitions` of the other node; - - Replace response if ``other.response`` is not ``None``; - - Update :py:attr:`pre_transition`, :py:attr:`pre_response` and :py:attr:`misc` dictionaries. + - Extend ``self.transitions`` with :py:attr:`transitions` of the other node; + - Replace response with ``other.response`` if ``self.response`` is ``None``; + - Dictionaries (:py:attr:`pre_transition`, :py:attr:`pre_response` and :py:attr:`misc`) + are appended to this node's dictionaries except for the repeating keys. + For example, ``inherit_from_other({1: 1, 3: 3}, {1: 0, 2: 2}) == {1: 1, 3: 3, 2: 2}``. + + Basically, only non-conflicting properties of ``other`` are inherited. """ - self.transitions = [*other.transitions, *self.transitions] - if other.response is not None: + + def merge_dicts(first: dict, second: dict): + first.update({k: v for k, v in second.items() if k not in first}) + + self.transitions.extend(other.transitions) + if self.response is None: self.response = other.response - self.pre_transition.update(**other.pre_transition) - self.pre_response.update(**other.pre_response) - self.misc.update(**other.misc) + merge_dicts(self.pre_transition, other.pre_transition) + merge_dicts(self.pre_response, other.pre_response) + merge_dicts(self.misc, other.misc) return self @@ -81,7 +89,10 @@ class Flow(BaseModel, extra="allow"): local_node: Node = Field( validation_alias=AliasChoices("local", "LOCAL", "local_node", "LOCAL_NODE"), default_factory=Node ) - """Node from which all other nodes in this Flow inherit properties according to :py:meth:`Node.merge`.""" + """ + Node from which all other nodes in this Flow inherit properties + according to :py:meth:`Node.inherit_from_other`. + """ __pydantic_extra__: Dict[str, Node] @property @@ -111,7 +122,10 @@ class Script(BaseModel, extra="allow"): global_node: Node = Field( validation_alias=AliasChoices("global", "GLOBAL", "global_node", "GLOBAL_NODE"), default_factory=Node ) - """Node from which all other nodes in this Script inherit properties according to :py:meth:`Node.merge`.""" + """ + Node from which all other nodes in this Script inherit properties + according to :py:meth:`Node.inherit_from_other`. + """ __pydantic_extra__: Dict[str, Flow] @property @@ -144,14 +158,14 @@ def get_node(self, label: AbsoluteNodeLabel) -> Optional[Node]: def get_inherited_node(self, label: AbsoluteNodeLabel) -> Optional[Node]: """ - Return a new node that inherits (using :py:meth:`Node.merge`) - properties from :py:attr:`Script.global_node`, :py:attr:`Flow.local_node` - and :py:class`Node`. + Return a new node that inherits (using :py:meth:`Node.inherit_from_other`) + properties from :py:class:`Node`, :py:attr:`Flow.local_node` + and :py:attr:`Script.global_node` (in that order). Flow and node are determined by ``label``. This is essentially a copy of the node specified by ``label``, - that inherits properties from `global_node` and `local_node`. + that inherits properties from ``local_node`` and ``global_node``. :return: A new node or ``None`` if it doesn't exist. """ @@ -164,7 +178,11 @@ def get_inherited_node(self, label: AbsoluteNodeLabel) -> Optional[Node]: inheritant_node = Node() - return inheritant_node.merge(self.global_node).merge(flow.local_node).merge(node) + return ( + inheritant_node.inherit_from_other(node) + .inherit_from_other(flow.local_node) + .inherit_from_other(self.global_node) + ) GLOBAL = "GLOBAL" diff --git a/tests/core/test_script.py b/tests/core/test_script.py index 0d565ddd1..37872d58a 100644 --- a/tests/core/test_script.py +++ b/tests/core/test_script.py @@ -11,41 +11,54 @@ async def call(self, ctx: Context) -> None: return -@pytest.mark.parametrize( - "first,second,result", - [ - ( - Node(transitions=[Tr(dst="node1"), Tr(dst="node2")]), - Node(transitions=[Tr(dst="node3"), Tr(dst="node4")]), - Node(transitions=[Tr(dst="node3"), Tr(dst="node4"), Tr(dst="node1"), Tr(dst="node2")]), - ), - ( - Node(response="msg1"), - Node(response="msg2"), - Node(response="msg2"), - ), - ( - Node(response="msg1"), - Node(), - Node(response="msg1"), - ), - ( - Node( - pre_response={"key": MyProcessing(value="1")}, - pre_transition={"key": MyProcessing(value="3")}, - misc={"k1": "v1"}, +class TestNodeMerge: + @pytest.mark.parametrize( + "first,second,result", + [ + ( + Node(transitions=[Tr(dst="node3"), Tr(dst="node4")]), + Node(transitions=[Tr(dst="node1"), Tr(dst="node2")]), + Node(transitions=[Tr(dst="node3"), Tr(dst="node4"), Tr(dst="node1"), Tr(dst="node2")]), ), - Node(pre_response={"key": MyProcessing(value="2")}, pre_transition={}, misc={"k2": "v2"}), - Node( - pre_response={"key": MyProcessing(value="2")}, - pre_transition={"key": MyProcessing(value="3")}, - misc={"k1": "v1", "k2": "v2"}, + ( + Node(response="msg2"), + Node(response="msg1"), + Node(response="msg2"), ), - ), - ], -) -def test_node_merge(first, second, result): - assert first.merge(second) == result + ( + Node(), + Node(response="msg1"), + Node(response="msg1"), + ), + ( + Node(pre_response={"key": MyProcessing(value="2")}, pre_transition={}, misc={"k2": "v2"}), + Node( + pre_response={"key": MyProcessing(value="1")}, + pre_transition={"key": MyProcessing(value="3")}, + misc={"k1": "v1"}, + ), + Node( + pre_response={"key": MyProcessing(value="2")}, + pre_transition={"key": MyProcessing(value="3")}, + misc={"k1": "v1", "k2": "v2"}, + ), + ), + ], + ) + def test_node_merge(self, first, second, result): + assert first.inherit_from_other(second) == result + + def test_dict_key_order(self): + global_node_dict = {"1": MyProcessing(value="1"), "3": MyProcessing(value="3")} + global_node = Node(pre_response=global_node_dict, pre_transition=global_node_dict, misc=global_node_dict) + local_node_dict = {"1": MyProcessing(value="1*"), "2": MyProcessing(value="2")} + local_node = Node(pre_response=local_node_dict, pre_transition=local_node_dict, misc=local_node_dict) + + result_node = local_node.model_copy().inherit_from_other(global_node) + + assert list(result_node.pre_response.keys()) == ["1", "2", "3"] + assert list(result_node.pre_transition.keys()) == ["1", "2", "3"] + assert list(result_node.misc.keys()) == ["1", "2", "3"] def test_flow_get_node(): @@ -71,14 +84,18 @@ def test_get_inherited_node(): global_node = Node(misc={"k1": "g1", "k2": "g2", "k3": "g3"}) local_node = Node(misc={"k2": "l1", "k3": "l2", "k4": "l3"}) node = Node(misc={"k3": "n1", "k4": "n2", "k5": "n3"}) + global_node_copy = global_node.model_copy(deep=True) + local_node_copy = local_node.model_copy(deep=True) + node_copy = node.model_copy(deep=True) + script = Script.model_validate({"global": global_node, "flow": {"local": local_node, "node": node}}) assert script.get_inherited_node(AbsoluteNodeLabel(flow_name="", node_name="")) is None assert script.get_inherited_node(AbsoluteNodeLabel(flow_name="flow", node_name="")) is None - assert script.get_inherited_node(AbsoluteNodeLabel(flow_name="flow", node_name="node")) == Node( - misc={"k1": "g1", "k2": "l1", "k3": "n1", "k4": "n2", "k5": "n3"} - ) + inherited_node = script.get_inherited_node(AbsoluteNodeLabel(flow_name="flow", node_name="node")) + assert inherited_node == Node(misc={"k1": "g1", "k2": "l1", "k3": "n1", "k4": "n2", "k5": "n3"}) + assert list(inherited_node.misc.keys()) == ["k3", "k4", "k5", "k2", "k1"] # assert not changed - assert script.global_node == global_node - assert script.get_flow("flow").local_node == local_node - assert script.get_node(AbsoluteNodeLabel(flow_name="flow", node_name="node")) == node + assert script.global_node == global_node_copy + assert script.get_flow("flow").local_node == local_node_copy + assert script.get_node(AbsoluteNodeLabel(flow_name="flow", node_name="node")) == node_copy diff --git a/tutorials/script/core/7_pre_response_processing.py b/tutorials/script/core/7_pre_response_processing.py index cc7cf9b14..b51fc86e2 100644 --- a/tutorials/script/core/7_pre_response_processing.py +++ b/tutorials/script/core/7_pre_response_processing.py @@ -146,14 +146,34 @@ async def modified_response(self, original_response, ctx): } +# %% [markdown] +""" +The order of execution for processing functions is as follows: + +1. All node-specific functions are executed in the order of definition; +2. All local functions are executed in the order of definition except those with + keys matching to previously executed functions; +3. All global functions are executed in the order of definition + except those with keys matching to previously executed functions. + +That means that if both global and local nodes +define a processing function with key "processing_name", +only the one inside the local node will be executed. + +This demonstrated in the happy path below +(the first prefix in the text is the last one to execute): +""" + + +# %% # testing happy_path = ( - (Message(), "l3_local: l2_local: l1_global: first"), + (Message(), "l1_global: l3_local: l2_local: first"), (Message(), "l3_local: l2_local: l1_step_1: second"), - (Message(), "l3_local: l2_step_2: l1_global: third"), - (Message(), "l3_step_3: l2_local: l1_global: fourth"), - (Message(), "l4_step_4: l3_local: l2_local: l1_global: fifth"), - (Message(), "l3_local: l2_local: l1_global: first"), + (Message(), "l1_global: l3_local: l2_step_2: third"), + (Message(), "l1_global: l2_local: l3_step_3: fourth"), + (Message(), "l1_global: l3_local: l2_local: l4_step_4: fifth"), + (Message(), "l1_global: l3_local: l2_local: first"), ) diff --git a/tutorials/script/core/8_misc.py b/tutorials/script/core/8_misc.py index a2dcf75c7..536671d57 100644 --- a/tutorials/script/core/8_misc.py +++ b/tutorials/script/core/8_misc.py @@ -92,30 +92,30 @@ async def call(self, ctx: Context) -> MessageInitTypes: ( Message(), "node_name=step_0: current_node.misc=" - "{'var1': 'global_data', " + "{'var3': 'this overwrites local values - step_0', " "'var2': 'global data is overwritten by local', " - "'var3': 'this overwrites local values - step_0'}", + "'var1': 'global_data'}", ), ( Message(), "node_name=step_1: current_node.misc=" - "{'var1': 'global_data', " + "{'var3': 'this overwrites local values - step_1', " "'var2': 'global data is overwritten by local', " - "'var3': 'this overwrites local values - step_1'}", + "'var1': 'global_data'}", ), ( Message(), "node_name=step_2: current_node.misc=" - "{'var1': 'global_data', " + "{'var3': 'this overwrites local values - step_2', " "'var2': 'global data is overwritten by local', " - "'var3': 'this overwrites local values - step_2'}", + "'var1': 'global_data'}", ), ( Message(), "node_name=step_0: current_node.misc=" - "{'var1': 'global_data', " + "{'var3': 'this overwrites local values - step_0', " "'var2': 'global data is overwritten by local', " - "'var3': 'this overwrites local values - step_0'}", + "'var1': 'global_data'}", ), ) From 6145a73f8b31e99fd61f57aaef8109dfc67054e6 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Sun, 8 Sep 2024 00:43:57 +0300 Subject: [PATCH 08/11] update copyright years --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b8d1a3d4f..6aac05f18 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,7 +16,7 @@ _distribution_metadata = importlib.metadata.metadata('chatsky') project = _distribution_metadata["Name"] -copyright = "2023, DeepPavlov" +copyright = "2022 - 2024, DeepPavlov" author = "DeepPavlov" release = _distribution_metadata["Version"] From 8649446eca13c82f6ea7f598169e35ec54d09e9c Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Sun, 8 Sep 2024 00:46:52 +0300 Subject: [PATCH 09/11] update logos --- README.md | 2 +- .../_static/images/Chatsky-full-dark.svg | 11 ++++++ .../_static/images/Chatsky-full-light.svg | 11 ++++++ .../_static/images/Chatsky-min-dark.svg | 4 ++ .../_static/images/Chatsky-min-light.svg | 4 ++ docs/source/_static/images/logo-dff.svg | 39 ------------------- docs/source/_static/images/logo-simple.svg | 39 ------------------- docs/source/conf.py | 10 ++--- 8 files changed, 34 insertions(+), 86 deletions(-) create mode 100644 docs/source/_static/images/Chatsky-full-dark.svg create mode 100644 docs/source/_static/images/Chatsky-full-light.svg create mode 100644 docs/source/_static/images/Chatsky-min-dark.svg create mode 100644 docs/source/_static/images/Chatsky-min-light.svg delete mode 100644 docs/source/_static/images/logo-dff.svg delete mode 100644 docs/source/_static/images/logo-simple.svg diff --git a/README.md b/README.md index 27d2a7b54..b73128864 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Chatsky +![Chatsky](docs/source/_static/images/Chatsky-full-dark.svg) [![Documentation Status](https://github.com/deeppavlov/chatsky/workflows/build_and_publish_docs/badge.svg?branch=dev)](https://deeppavlov.github.io/chatsky) [![Codestyle](https://github.com/deeppavlov/chatsky/workflows/codestyle/badge.svg?branch=dev)](https://github.com/deeppavlov/chatsky/actions/workflows/codestyle.yml) diff --git a/docs/source/_static/images/Chatsky-full-dark.svg b/docs/source/_static/images/Chatsky-full-dark.svg new file mode 100644 index 000000000..0a63ad937 --- /dev/null +++ b/docs/source/_static/images/Chatsky-full-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/source/_static/images/Chatsky-full-light.svg b/docs/source/_static/images/Chatsky-full-light.svg new file mode 100644 index 000000000..44e440fd8 --- /dev/null +++ b/docs/source/_static/images/Chatsky-full-light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/source/_static/images/Chatsky-min-dark.svg b/docs/source/_static/images/Chatsky-min-dark.svg new file mode 100644 index 000000000..0d91ec949 --- /dev/null +++ b/docs/source/_static/images/Chatsky-min-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/source/_static/images/Chatsky-min-light.svg b/docs/source/_static/images/Chatsky-min-light.svg new file mode 100644 index 000000000..044cd1b95 --- /dev/null +++ b/docs/source/_static/images/Chatsky-min-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/source/_static/images/logo-dff.svg b/docs/source/_static/images/logo-dff.svg deleted file mode 100644 index b2f644b0c..000000000 --- a/docs/source/_static/images/logo-dff.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/source/_static/images/logo-simple.svg b/docs/source/_static/images/logo-simple.svg deleted file mode 100644 index b2f644b0c..000000000 --- a/docs/source/_static/images/logo-simple.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/source/conf.py b/docs/source/conf.py index 6aac05f18..0edee636a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -94,10 +94,10 @@ :tutorial_name: {{ env.docname }} """ -html_logo = "_static/images/logo-simple.svg" +html_logo = "_static/images/Chatsky-full-dark.svg" nbsphinx_thumbnails = { - "tutorials/*": "_static/images/logo-simple.svg", + "tutorials/*": "_static/images/Chatsky-min-light.svg", } html_context = { @@ -114,10 +114,6 @@ # Theme options html_theme_options = { "header_links_before_dropdown": 5, - "logo": { - "alt_text": "Chatsky logo (simple and nice)", - "text": "Chatsky", - }, "icon_links": [ { "name": "DeepPavlov Forum", @@ -143,7 +139,7 @@ favicons = [ - {"href": "images/logo-dff.svg"}, + {"href": "images/Chatsky-min-light.svg"}, ] From 862d6194f457dbfe4f7afeed9d8694b1b158c6f8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Sun, 8 Sep 2024 00:48:28 +0300 Subject: [PATCH 10/11] update project version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a6ff0a967..146665fff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "chatsky" -version = "0.8.0" +version = "1.0.0rc1" description = "Chatsky is a free and open-source software stack for creating chatbots, released under the terms of Apache License 2.0." license = "Apache-2.0" authors = [ From 8eb9acffe4b8b20bc525d62f67b0bc5ba7217dda Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Sun, 8 Sep 2024 00:51:30 +0300 Subject: [PATCH 11/11] update lock file --- poetry.lock | 2929 ++++++++++++++++++++++++++------------------------- 1 file changed, 1503 insertions(+), 1426 deletions(-) diff --git a/poetry.lock b/poetry.lock index ad192e6a2..133a63c27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -45,91 +45,118 @@ files = [ ] [[package]] -name = "aiohttp" -version = "3.9.5" -description = "Async http client/server framework (asyncio)" +name = "aiohappyeyeballs" +version = "2.4.0" +description = "Happy Eyeballs for asyncio" optional = true python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, - {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, - {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, - {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, - {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, - {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, - {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, - {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, - {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, - {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, - {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, - {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, - {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, - {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, - {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, - {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, - {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, - {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, - {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, - {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, - {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, - {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, - {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, - {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, - {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, - {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, - {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, ] -[package.dependencies] +[[package]] +name = "aiohttp" +version = "3.10.5" +description = "Async http client/server framework (asyncio)" +optional = true +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" @@ -138,7 +165,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiolimiter" @@ -196,26 +223,26 @@ files = [ [[package]] name = "altair" -version = "5.4.0" +version = "5.4.1" description = "Vega-Altair: A declarative statistical visualization library for Python." optional = false python-versions = ">=3.8" files = [ - {file = "altair-5.4.0-py3-none-any.whl", hash = "sha256:86be974867007cfdf5c92d6f89926535546a4d00e0ea6c1745ef4d5937aad9df"}, - {file = "altair-5.4.0.tar.gz", hash = "sha256:27c69e93d85b7bb3c98fa3626ef7e6bc6939a1466a55a8f8bf68c4bff31cf030"}, + {file = "altair-5.4.1-py3-none-any.whl", hash = "sha256:0fb130b8297a569d08991fb6fe763582e7569f8a04643bbd9212436e3be04aef"}, + {file = "altair-5.4.1.tar.gz", hash = "sha256:0ce8c2e66546cb327e5f2d7572ec0e7c6feece816203215613962f0ec1d76a82"}, ] [package.dependencies] jinja2 = "*" jsonschema = ">=3.0" -narwhals = ">=1.1.0" +narwhals = ">=1.5.2" packaging = "*" typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] all = ["altair-tiles (>=0.3.0)", "anywidget (>=0.9.0)", "numpy", "pandas (>=0.25.3)", "pyarrow (>=11)", "vega-datasets (>=0.9.0)", "vegafusion[embed] (>=1.6.6)", "vl-convert-python (>=1.6.0)"] -dev = ["geopandas", "hatch", "ibis-framework[polars]", "ipython[kernel]", "mistune", "mypy", "pandas (>=0.25.3)", "pandas-stubs", "polars (>=0.20.3)", "pytest", "pytest-cov", "pytest-xdist[psutil] (>=3.5,<4.0)", "ruff (>=0.5.7)", "types-jsonschema", "types-setuptools"] -doc = ["docutils", "jinja2", "myst-parser", "numpydoc", "pillow (>=9,<10)", "pydata-sphinx-theme (>=0.14.1)", "scipy", "sphinx (>=8.0.0)", "sphinx-copybutton", "sphinx-design", "sphinxext-altair"] +dev = ["geopandas", "hatch", "ibis-framework[polars]", "ipython[kernel]", "mistune", "mypy", "pandas (>=0.25.3)", "pandas-stubs", "polars (>=0.20.3)", "pytest", "pytest-cov", "pytest-xdist[psutil] (>=3.5,<4.0)", "ruff (>=0.6.0)", "types-jsonschema", "types-setuptools"] +doc = ["docutils", "jinja2", "myst-parser", "numpydoc", "pillow (>=9,<10)", "pydata-sphinx-theme (>=0.14.1)", "scipy", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinxext-altair"] [[package]] name = "annotated-types" @@ -563,32 +590,32 @@ test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "babel" -version = "2.15.0" +version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" files = [ - {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, - {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.dependencies] @@ -826,13 +853,13 @@ files = [ [[package]] name = "build" -version = "1.2.1" +version = "1.2.2" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.8" files = [ - {file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"}, - {file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"}, + {file = "build-1.2.2-py3-none-any.whl", hash = "sha256:277ccc71619d98afdd841a0e96ac9fe1593b823af481d3b0cea748e8894e0613"}, + {file = "build-1.2.2.tar.gz", hash = "sha256:119b2fb462adef986483438377a13b2f42064a2a3a4161f24a0cca698a07ac8c"}, ] [package.dependencies] @@ -872,100 +899,100 @@ redis = ["redis (>=2.10.5)"] [[package]] name = "cachetools" -version = "5.3.3" +version = "5.5.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.17.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, - {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, - {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, - {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, - {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, - {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, - {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, - {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, - {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, - {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, - {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, - {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, - {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, - {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, - {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, - {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -1253,38 +1280,38 @@ files = [ [[package]] name = "cryptography" -version = "43.0.0" +version = "43.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, - {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, - {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, - {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, - {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, - {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, - {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, ] [package.dependencies] @@ -1297,38 +1324,38 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "debugpy" -version = "1.8.2" +version = "1.8.5" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"}, - {file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"}, - {file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"}, - {file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"}, - {file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"}, - {file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"}, - {file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"}, - {file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"}, - {file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"}, - {file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"}, - {file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"}, - {file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"}, - {file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"}, - {file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"}, - {file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"}, - {file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"}, - {file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"}, - {file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"}, - {file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"}, - {file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"}, - {file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"}, - {file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"}, + {file = "debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7"}, + {file = "debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a"}, + {file = "debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed"}, + {file = "debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e"}, + {file = "debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a"}, + {file = "debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b"}, + {file = "debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408"}, + {file = "debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3"}, + {file = "debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156"}, + {file = "debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb"}, + {file = "debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7"}, + {file = "debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c"}, + {file = "debugpy-1.8.5-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a"}, + {file = "debugpy-1.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226"}, + {file = "debugpy-1.8.5-cp38-cp38-win32.whl", hash = "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a"}, + {file = "debugpy-1.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf"}, + {file = "debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c"}, + {file = "debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"}, + {file = "debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34"}, + {file = "debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c"}, + {file = "debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44"}, + {file = "debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0"}, ] [[package]] @@ -1515,13 +1542,13 @@ tests = ["pytest"] [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -1543,13 +1570,13 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "executing" -version = "2.0.1" +version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, ] [package.extras] @@ -1557,23 +1584,23 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "fastapi" -version = "0.112.0" +version = "0.114.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.112.0-py3-none-any.whl", hash = "sha256:3487ded9778006a45834b8c816ec4a48d522e2631ca9e75ec5a774f1b052f821"}, - {file = "fastapi-0.112.0.tar.gz", hash = "sha256:d262bc56b7d101d1f4e8fc0ad2ac75bb9935fec504d2b7117686cec50710cf05"}, + {file = "fastapi-0.114.0-py3-none-any.whl", hash = "sha256:fee75aa1b1d3d73f79851c432497e4394e413e1dece6234f68d3ce250d12760a"}, + {file = "fastapi-0.114.0.tar.gz", hash = "sha256:9908f2a5cc733004de6ca5e1412698f35085cefcbfd41d539245b9edf87b73c1"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.38.0" +starlette = ">=0.37.2,<0.39.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "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)"] -standard = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "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)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "fastjsonschema" @@ -1591,29 +1618,29 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.15.4" +version = "3.16.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, + {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"}, + {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "flake8" -version = "7.1.0" +version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, - {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, ] [package.dependencies] @@ -1647,13 +1674,13 @@ dotenv = ["python-dotenv"] [[package]] name = "flask-cors" -version = "4.0.1" +version = "5.0.0" description = "A Flask extension adding a decorator for CORS support" optional = false python-versions = "*" files = [ - {file = "Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677"}, - {file = "flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4"}, + {file = "Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"}, + {file = "flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef"}, ] [package.dependencies] @@ -1994,13 +2021,13 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", [[package]] name = "googleapis-common-protos" -version = "1.63.2" +version = "1.65.0" description = "Common protobufs used in Google APIs" optional = true python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, - {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, + {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, + {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, ] [package.dependencies] @@ -2082,61 +2109,61 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.64.1" +version = "1.66.1" description = "HTTP/2-based RPC framework" optional = true python-versions = ">=3.8" files = [ - {file = "grpcio-1.64.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:55697ecec192bc3f2f3cc13a295ab670f51de29884ca9ae6cd6247df55df2502"}, - {file = "grpcio-1.64.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3b64ae304c175671efdaa7ec9ae2cc36996b681eb63ca39c464958396697daff"}, - {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:bac71b4b28bc9af61efcdc7630b166440bbfbaa80940c9a697271b5e1dabbc61"}, - {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c024ffc22d6dc59000faf8ad781696d81e8e38f4078cb0f2630b4a3cf231a90"}, - {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7cd5c1325f6808b8ae31657d281aadb2a51ac11ab081ae335f4f7fc44c1721d"}, - {file = "grpcio-1.64.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0a2813093ddb27418a4c99f9b1c223fab0b053157176a64cc9db0f4557b69bd9"}, - {file = "grpcio-1.64.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2981c7365a9353f9b5c864595c510c983251b1ab403e05b1ccc70a3d9541a73b"}, - {file = "grpcio-1.64.1-cp310-cp310-win32.whl", hash = "sha256:1262402af5a511c245c3ae918167eca57342c72320dffae5d9b51840c4b2f86d"}, - {file = "grpcio-1.64.1-cp310-cp310-win_amd64.whl", hash = "sha256:19264fc964576ddb065368cae953f8d0514ecc6cb3da8903766d9fb9d4554c33"}, - {file = "grpcio-1.64.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:58b1041e7c870bb30ee41d3090cbd6f0851f30ae4eb68228955d973d3efa2e61"}, - {file = "grpcio-1.64.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bbc5b1d78a7822b0a84c6f8917faa986c1a744e65d762ef6d8be9d75677af2ca"}, - {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5841dd1f284bd1b3d8a6eca3a7f062b06f1eec09b184397e1d1d43447e89a7ae"}, - {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8caee47e970b92b3dd948371230fcceb80d3f2277b3bf7fbd7c0564e7d39068e"}, - {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73819689c169417a4f978e562d24f2def2be75739c4bed1992435d007819da1b"}, - {file = "grpcio-1.64.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6503b64c8b2dfad299749cad1b595c650c91e5b2c8a1b775380fcf8d2cbba1e9"}, - {file = "grpcio-1.64.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1de403fc1305fd96cfa75e83be3dee8538f2413a6b1685b8452301c7ba33c294"}, - {file = "grpcio-1.64.1-cp311-cp311-win32.whl", hash = "sha256:d4d29cc612e1332237877dfa7fe687157973aab1d63bd0f84cf06692f04c0367"}, - {file = "grpcio-1.64.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e56462b05a6f860b72f0fa50dca06d5b26543a4e88d0396259a07dc30f4e5aa"}, - {file = "grpcio-1.64.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:4657d24c8063e6095f850b68f2d1ba3b39f2b287a38242dcabc166453e950c59"}, - {file = "grpcio-1.64.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:62b4e6eb7bf901719fce0ca83e3ed474ae5022bb3827b0a501e056458c51c0a1"}, - {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:ee73a2f5ca4ba44fa33b4d7d2c71e2c8a9e9f78d53f6507ad68e7d2ad5f64a22"}, - {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:198908f9b22e2672a998870355e226a725aeab327ac4e6ff3a1399792ece4762"}, - {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39b9d0acaa8d835a6566c640f48b50054f422d03e77e49716d4c4e8e279665a1"}, - {file = "grpcio-1.64.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5e42634a989c3aa6049f132266faf6b949ec2a6f7d302dbb5c15395b77d757eb"}, - {file = "grpcio-1.64.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1a82e0b9b3022799c336e1fc0f6210adc019ae84efb7321d668129d28ee1efb"}, - {file = "grpcio-1.64.1-cp312-cp312-win32.whl", hash = "sha256:55260032b95c49bee69a423c2f5365baa9369d2f7d233e933564d8a47b893027"}, - {file = "grpcio-1.64.1-cp312-cp312-win_amd64.whl", hash = "sha256:c1a786ac592b47573a5bb7e35665c08064a5d77ab88a076eec11f8ae86b3e3f6"}, - {file = "grpcio-1.64.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:a011ac6c03cfe162ff2b727bcb530567826cec85eb8d4ad2bfb4bd023287a52d"}, - {file = "grpcio-1.64.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4d6dab6124225496010bd22690f2d9bd35c7cbb267b3f14e7a3eb05c911325d4"}, - {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a5e771d0252e871ce194d0fdcafd13971f1aae0ddacc5f25615030d5df55c3a2"}, - {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3c1b90ab93fed424e454e93c0ed0b9d552bdf1b0929712b094f5ecfe7a23ad"}, - {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20405cb8b13fd779135df23fabadc53b86522d0f1cba8cca0e87968587f50650"}, - {file = "grpcio-1.64.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0cc79c982ccb2feec8aad0e8fb0d168bcbca85bc77b080d0d3c5f2f15c24ea8f"}, - {file = "grpcio-1.64.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a3a035c37ce7565b8f4f35ff683a4db34d24e53dc487e47438e434eb3f701b2a"}, - {file = "grpcio-1.64.1-cp38-cp38-win32.whl", hash = "sha256:1257b76748612aca0f89beec7fa0615727fd6f2a1ad580a9638816a4b2eb18fd"}, - {file = "grpcio-1.64.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a12ddb1678ebc6a84ec6b0487feac020ee2b1659cbe69b80f06dbffdb249122"}, - {file = "grpcio-1.64.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:75dbbf415026d2862192fe1b28d71f209e2fd87079d98470db90bebe57b33179"}, - {file = "grpcio-1.64.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e3d9f8d1221baa0ced7ec7322a981e28deb23749c76eeeb3d33e18b72935ab62"}, - {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5f8b75f64d5d324c565b263c67dbe4f0af595635bbdd93bb1a88189fc62ed2e5"}, - {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c84ad903d0d94311a2b7eea608da163dace97c5fe9412ea311e72c3684925602"}, - {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940e3ec884520155f68a3b712d045e077d61c520a195d1a5932c531f11883489"}, - {file = "grpcio-1.64.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f10193c69fc9d3d726e83bbf0f3d316f1847c3071c8c93d8090cf5f326b14309"}, - {file = "grpcio-1.64.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac15b6c2c80a4d1338b04d42a02d376a53395ddf0ec9ab157cbaf44191f3ffdd"}, - {file = "grpcio-1.64.1-cp39-cp39-win32.whl", hash = "sha256:03b43d0ccf99c557ec671c7dede64f023c7da9bb632ac65dbc57f166e4970040"}, - {file = "grpcio-1.64.1-cp39-cp39-win_amd64.whl", hash = "sha256:ed6091fa0adcc7e4ff944090cf203a52da35c37a130efa564ded02b7aff63bcd"}, - {file = "grpcio-1.64.1.tar.gz", hash = "sha256:8d51dd1c59d5fa0f34266b80a3805ec29a1f26425c2a54736133f6d87fc4968a"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.64.1)"] + {file = "grpcio-1.66.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:4877ba180591acdf127afe21ec1c7ff8a5ecf0fe2600f0d3c50e8c4a1cbc6492"}, + {file = "grpcio-1.66.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3750c5a00bd644c75f4507f77a804d0189d97a107eb1481945a0cf3af3e7a5ac"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:a013c5fbb12bfb5f927444b477a26f1080755a931d5d362e6a9a720ca7dbae60"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1b24c23d51a1e8790b25514157d43f0a4dce1ac12b3f0b8e9f66a5e2c4c132f"}, + {file = "grpcio-1.66.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7ffb8ea674d68de4cac6f57d2498fef477cef582f1fa849e9f844863af50083"}, + {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:307b1d538140f19ccbd3aed7a93d8f71103c5d525f3c96f8616111614b14bf2a"}, + {file = "grpcio-1.66.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c17ebcec157cfb8dd445890a03e20caf6209a5bd4ac5b040ae9dbc59eef091d"}, + {file = "grpcio-1.66.1-cp310-cp310-win32.whl", hash = "sha256:ef82d361ed5849d34cf09105d00b94b6728d289d6b9235513cb2fcc79f7c432c"}, + {file = "grpcio-1.66.1-cp310-cp310-win_amd64.whl", hash = "sha256:292a846b92cdcd40ecca46e694997dd6b9be6c4c01a94a0dfb3fcb75d20da858"}, + {file = "grpcio-1.66.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:c30aeceeaff11cd5ddbc348f37c58bcb96da8d5aa93fed78ab329de5f37a0d7a"}, + {file = "grpcio-1.66.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a1e224ce6f740dbb6b24c58f885422deebd7eb724aff0671a847f8951857c26"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a66fe4dc35d2330c185cfbb42959f57ad36f257e0cc4557d11d9f0a3f14311df"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3ba04659e4fce609de2658fe4dbf7d6ed21987a94460f5f92df7579fd5d0e22"}, + {file = "grpcio-1.66.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4573608e23f7e091acfbe3e84ac2045680b69751d8d67685ffa193a4429fedb1"}, + {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7e06aa1f764ec8265b19d8f00140b8c4b6ca179a6dc67aa9413867c47e1fb04e"}, + {file = "grpcio-1.66.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3885f037eb11f1cacc41f207b705f38a44b69478086f40608959bf5ad85826dd"}, + {file = "grpcio-1.66.1-cp311-cp311-win32.whl", hash = "sha256:97ae7edd3f3f91480e48ede5d3e7d431ad6005bfdbd65c1b56913799ec79e791"}, + {file = "grpcio-1.66.1-cp311-cp311-win_amd64.whl", hash = "sha256:cfd349de4158d797db2bd82d2020554a121674e98fbe6b15328456b3bf2495bb"}, + {file = "grpcio-1.66.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:a92c4f58c01c77205df6ff999faa008540475c39b835277fb8883b11cada127a"}, + {file = "grpcio-1.66.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fdb14bad0835914f325349ed34a51940bc2ad965142eb3090081593c6e347be9"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f03a5884c56256e08fd9e262e11b5cfacf1af96e2ce78dc095d2c41ccae2c80d"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2559692d8e7e245d456877a85ee41525f3ed425aa97eb7a70fc9a79df91a0"}, + {file = "grpcio-1.66.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca1be089fb4446490dd1135828bd42a7c7f8421e74fa581611f7afdf7ab761"}, + {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d639c939ad7c440c7b2819a28d559179a4508783f7e5b991166f8d7a34b52815"}, + {file = "grpcio-1.66.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9feb4e5ec8dc2d15709f4d5fc367794d69277f5d680baf1910fc9915c633524"}, + {file = "grpcio-1.66.1-cp312-cp312-win32.whl", hash = "sha256:7101db1bd4cd9b880294dec41a93fcdce465bdbb602cd8dc5bd2d6362b618759"}, + {file = "grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734"}, + {file = "grpcio-1.66.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:ecfe735e7a59e5a98208447293ff8580e9db1e890e232b8b292dc8bd15afc0d2"}, + {file = "grpcio-1.66.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4825a3aa5648010842e1c9d35a082187746aa0cdbf1b7a2a930595a94fb10fce"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f517fd7259fe823ef3bd21e508b653d5492e706e9f0ef82c16ce3347a8a5620c"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1fe60d0772831d96d263b53d83fb9a3d050a94b0e94b6d004a5ad111faa5b5b"}, + {file = "grpcio-1.66.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31a049daa428f928f21090403e5d18ea02670e3d5d172581670be006100db9ef"}, + {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f914386e52cbdeb5d2a7ce3bf1fdfacbe9d818dd81b6099a05b741aaf3848bb"}, + {file = "grpcio-1.66.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bff2096bdba686019fb32d2dde45b95981f0d1490e054400f70fc9a8af34b49d"}, + {file = "grpcio-1.66.1-cp38-cp38-win32.whl", hash = "sha256:aa8ba945c96e73de29d25331b26f3e416e0c0f621e984a3ebdb2d0d0b596a3b3"}, + {file = "grpcio-1.66.1-cp38-cp38-win_amd64.whl", hash = "sha256:161d5c535c2bdf61b95080e7f0f017a1dfcb812bf54093e71e5562b16225b4ce"}, + {file = "grpcio-1.66.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:d0cd7050397b3609ea51727b1811e663ffda8bda39c6a5bb69525ef12414b503"}, + {file = "grpcio-1.66.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0e6c9b42ded5d02b6b1fea3a25f036a2236eeb75d0579bfd43c0018c88bf0a3e"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c9f80f9fad93a8cf71c7f161778ba47fd730d13a343a46258065c4deb4b550c0"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dd67ed9da78e5121efc5c510f0122a972216808d6de70953a740560c572eb44"}, + {file = "grpcio-1.66.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48b0d92d45ce3be2084b92fb5bae2f64c208fea8ceed7fccf6a7b524d3c4942e"}, + {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d813316d1a752be6f5c4360c49f55b06d4fe212d7df03253dfdae90c8a402bb"}, + {file = "grpcio-1.66.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c9bebc6627873ec27a70fc800f6083a13c70b23a5564788754b9ee52c5aef6c"}, + {file = "grpcio-1.66.1-cp39-cp39-win32.whl", hash = "sha256:30a1c2cf9390c894c90bbc70147f2372130ad189cffef161f0432d0157973f45"}, + {file = "grpcio-1.66.1-cp39-cp39-win_amd64.whl", hash = "sha256:17663598aadbedc3cacd7bbde432f541c8e07d2496564e22b214b22c7523dac8"}, + {file = "grpcio-1.66.1.tar.gz", hash = "sha256:35334f9c9745add3e357e3372756fd32d925bd52c41da97f4dfdafbde0bf0ee2"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.66.1)"] [[package]] name = "h11" @@ -2198,13 +2225,13 @@ trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httpx" -version = "0.27.0" +version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, - {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, + {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, + {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, ] [package.dependencies] @@ -2221,6 +2248,7 @@ brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "humanize" @@ -2249,13 +2277,13 @@ files = [ [[package]] name = "idna" -version = "3.7" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] @@ -2271,40 +2299,44 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.1.0" +version = "8.4.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, + {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, + {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "importlib-resources" -version = "6.4.0" +version = "6.4.4" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, - {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, + {file = "importlib_resources-6.4.4-py3-none-any.whl", hash = "sha256:dda242603d1c9cd836c3368b1174ed74cb4049ecd209e7a1a0104620c18c5c11"}, + {file = "importlib_resources-6.4.4.tar.gz", hash = "sha256:20600c8b7361938dc0bb2d5ec0297802e575df486f5a544fa414da65e13721f7"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -2330,13 +2362,13 @@ files = [ [[package]] name = "ipykernel" -version = "6.29.4" +version = "6.29.5" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" files = [ - {file = "ipykernel-6.29.4-py3-none-any.whl", hash = "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da"}, - {file = "ipykernel-6.29.4.tar.gz", hash = "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c"}, + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, ] [package.dependencies] @@ -2402,21 +2434,21 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa [[package]] name = "ipywidgets" -version = "8.1.3" +version = "8.1.5" description = "Jupyter interactive widgets" optional = false python-versions = ">=3.7" files = [ - {file = "ipywidgets-8.1.3-py3-none-any.whl", hash = "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2"}, - {file = "ipywidgets-8.1.3.tar.gz", hash = "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c"}, + {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, + {file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"}, ] [package.dependencies] comm = ">=0.1.3" ipython = ">=6.1.0" -jupyterlab-widgets = ">=3.0.11,<3.1.0" +jupyterlab-widgets = ">=3.0.12,<3.1.0" traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0.11,<4.1.0" +widgetsnbextension = ">=4.0.12,<4.1.0" [package.extras] test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] @@ -2599,23 +2631,22 @@ referencing = ">=0.31.0" [[package]] name = "jupyter" -version = "1.0.0" +version = "1.1.1" description = "Jupyter metapackage. Install all the Jupyter components in one go." optional = false python-versions = "*" files = [ - {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, - {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, - {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, + {file = "jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83"}, + {file = "jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a"}, ] [package.dependencies] ipykernel = "*" ipywidgets = "*" jupyter-console = "*" +jupyterlab = "*" nbconvert = "*" notebook = "*" -qtconsole = "*" [[package]] name = "jupyter-client" @@ -2726,13 +2757,13 @@ jupyter-server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.14.1" +version = "2.14.2" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.14.1-py3-none-any.whl", hash = "sha256:16f7177c3a4ea8fe37784e2d31271981a812f0b2874af17339031dc3510cc2a5"}, - {file = "jupyter_server-2.14.1.tar.gz", hash = "sha256:12558d158ec7a0653bf96cc272bc7ad79e0127d503b982ed144399346694f726"}, + {file = "jupyter_server-2.14.2-py3-none-any.whl", hash = "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd"}, + {file = "jupyter_server-2.14.2.tar.gz", hash = "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b"}, ] [package.dependencies] @@ -2781,13 +2812,13 @@ test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (> [[package]] name = "jupyterlab" -version = "4.2.3" +version = "4.2.5" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.2.3-py3-none-any.whl", hash = "sha256:0b59d11808e84bb84105c73364edfa867dd475492429ab34ea388a52f2e2e596"}, - {file = "jupyterlab-4.2.3.tar.gz", hash = "sha256:df6e46969ea51d66815167f23d92f105423b7f1f06fa604d4f44aeb018c82c7b"}, + {file = "jupyterlab-4.2.5-py3-none-any.whl", hash = "sha256:73b6e0775d41a9fee7ee756c80f58a6bed4040869ccc21411dc559818874d321"}, + {file = "jupyterlab-4.2.5.tar.gz", hash = "sha256:ae7f3a1b8cb88b4f55009ce79fa7c06f99d70cd63601ee4aa91815d054f46f75"}, ] [package.dependencies] @@ -2813,7 +2844,7 @@ dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<7.3.0)", "sphinx-copybutton"] docs-screenshots = ["altair (==5.3.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.2)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.1.post2)", "matplotlib (==3.8.3)", "nbconvert (>=7.0.0)", "pandas (==2.2.1)", "scipy (==1.12.0)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] -upgrade-extension = ["copier (>=8,<10)", "jinja2-time (<0.3)", "pydantic (<2.0)", "pyyaml-include (<2.0)", "tomli-w (<2.0)"] +upgrade-extension = ["copier (>=9,<10)", "jinja2-time (<0.3)", "pydantic (<3.0)", "pyyaml-include (<3.0)", "tomli-w (<2.0)"] [[package]] name = "jupyterlab-pygments" @@ -2828,13 +2859,13 @@ files = [ [[package]] name = "jupyterlab-server" -version = "2.27.2" +version = "2.27.3" description = "A set of server components for JupyterLab and JupyterLab like applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab_server-2.27.2-py3-none-any.whl", hash = "sha256:54aa2d64fd86383b5438d9f0c032f043c4d8c0264b8af9f60bd061157466ea43"}, - {file = "jupyterlab_server-2.27.2.tar.gz", hash = "sha256:15cbb349dc45e954e09bacf81b9f9bcb10815ff660fb2034ecd7417db3a7ea27"}, + {file = "jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4"}, + {file = "jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"}, ] [package.dependencies] @@ -2854,24 +2885,24 @@ test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-v [[package]] name = "jupyterlab-widgets" -version = "3.0.11" +version = "3.0.13" description = "Jupyter interactive widgets for JupyterLab" optional = false python-versions = ">=3.7" files = [ - {file = "jupyterlab_widgets-3.0.11-py3-none-any.whl", hash = "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0"}, - {file = "jupyterlab_widgets-3.0.11.tar.gz", hash = "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27"}, + {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, + {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, ] [[package]] name = "jupytext" -version = "1.16.2" +version = "1.16.4" description = "Jupyter notebooks as Markdown documents, Julia, Python or R scripts" optional = false python-versions = ">=3.8" files = [ - {file = "jupytext-1.16.2-py3-none-any.whl", hash = "sha256:197a43fef31dca612b68b311e01b8abd54441c7e637810b16b6cb8f2ab66065e"}, - {file = "jupytext-1.16.2.tar.gz", hash = "sha256:8627dd9becbbebd79cc4a4ed4727d89d78e606b4b464eab72357b3b029023a14"}, + {file = "jupytext-1.16.4-py3-none-any.whl", hash = "sha256:76989d2690e65667ea6fb411d8056abe7cd0437c07bd774660b83d62acf9490a"}, + {file = "jupytext-1.16.4.tar.gz", hash = "sha256:28e33f46f2ce7a41fb9d677a4a2c95327285579b64ca104437c4b9eb1e4174e9"}, ] [package.dependencies] @@ -2883,11 +2914,11 @@ pyyaml = "*" tomli = {version = "*", markers = "python_version < \"3.11\""} [package.extras] -dev = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"] +dev = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (>=1.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"] docs = ["myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] test = ["pytest", "pytest-randomly", "pytest-xdist"] test-cov = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist"] -test-external = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"] +test-external = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (>=1.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"] test-functional = ["pytest", "pytest-randomly", "pytest-xdist"] test-integration = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-randomly", "pytest-xdist"] test-ui = ["calysto-bash"] @@ -3120,24 +3151,24 @@ test = ["pytest", "pytest-cov"] [[package]] name = "more-itertools" -version = "10.3.0" +version = "10.5.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, - {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, + {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, + {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, ] [[package]] name = "motor" -version = "3.5.0" +version = "3.5.1" description = "Non-blocking MongoDB driver for Tornado or asyncio" optional = true python-versions = ">=3.8" files = [ - {file = "motor-3.5.0-py3-none-any.whl", hash = "sha256:e8f1d7a3370e8dd30eb4c68aaaee46dc608fbac70a757e58f3e828124f5e7693"}, - {file = "motor-3.5.0.tar.gz", hash = "sha256:2b38e405e5a0c52d499edb8d23fa029debdf0158da092c21b44d92cac7f59942"}, + {file = "motor-3.5.1-py3-none-any.whl", hash = "sha256:f95a9ea0f011464235e0bd72910baa291db3a6009e617ac27b82f57885abafb8"}, + {file = "motor-3.5.1.tar.gz", hash = "sha256:1622bd7b39c3e6375607c14736f6e1d498128eadf6f5f93f8786cf17d37062ac"}, ] [package.dependencies] @@ -3319,38 +3350,38 @@ files = [ [[package]] name = "mypy" -version = "1.11.1" +version = "1.11.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, - {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, - {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, - {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, - {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, - {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, - {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, - {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, - {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, - {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, - {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, - {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, - {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, - {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, - {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, - {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, - {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, - {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, - {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, ] [package.dependencies] @@ -3377,16 +3408,19 @@ files = [ [[package]] name = "narwhals" -version = "1.3.0" +version = "1.6.2" description = "Extremely lightweight compatibility layer between dataframe libraries" optional = false python-versions = ">=3.8" files = [ - {file = "narwhals-1.3.0-py3-none-any.whl", hash = "sha256:b8a0f191d5e94927cbbeb52dc3546272e59511f5c0e0733a2dd8e448933f193f"}, - {file = "narwhals-1.3.0.tar.gz", hash = "sha256:e30408c88c401de3c65a98edd7a444e181adfe7dec715cd9aa2899cc118e98a1"}, + {file = "narwhals-1.6.2-py3-none-any.whl", hash = "sha256:f236fe14300dd85d877a8c05eb861805e2e0076c14b1d24af66d02fa98c245b6"}, + {file = "narwhals-1.6.2.tar.gz", hash = "sha256:caee5b13a62740787fa69fc7f13fb119e0d36a7a1f8797a70c2ec19bcecc0b5a"}, ] [package.extras] +cudf = ["cudf (>=23.08.00)"] +dask = ["dask[dataframe] (>=2024.7)"] +modin = ["modin"] pandas = ["pandas (>=0.25.3)"] polars = ["polars (>=0.20.3)"] pyarrow = ["pyarrow (>=11.0.0)"] @@ -3474,13 +3508,13 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] [[package]] name = "nbsphinx" -version = "0.9.4" +version = "0.9.5" description = "Jupyter Notebook Tools for Sphinx" optional = false python-versions = ">=3.6" files = [ - {file = "nbsphinx-0.9.4-py3-none-any.whl", hash = "sha256:22cb1d974a8300e8118ca71aea1f649553743c0c5830a54129dcd446e6a8ba17"}, - {file = "nbsphinx-0.9.4.tar.gz", hash = "sha256:042a60806fc23d519bc5bef59d95570713913fe442fda759d53e3aaf62104794"}, + {file = "nbsphinx-0.9.5-py3-none-any.whl", hash = "sha256:d82f71084425db1f48e72515f15c25b4de8652ceaab513ee462ac05f1b8eae0a"}, + {file = "nbsphinx-0.9.5.tar.gz", hash = "sha256:736916e7b0dab28fc904f4a9ae3b53a9a50c29fccc6329c052fcc7485abcf2b7"}, ] [package.dependencies] @@ -3504,13 +3538,13 @@ files = [ [[package]] name = "notebook" -version = "7.2.1" +version = "7.2.2" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.8" files = [ - {file = "notebook-7.2.1-py3-none-any.whl", hash = "sha256:f45489a3995746f2195a137e0773e2130960b51c9ac3ce257dbc2705aab3a6ca"}, - {file = "notebook-7.2.1.tar.gz", hash = "sha256:4287b6da59740b32173d01d641f763d292f49c30e7a51b89c46ba8473126341e"}, + {file = "notebook-7.2.2-py3-none-any.whl", hash = "sha256:c89264081f671bc02eec0ed470a627ed791b9156cad9285226b31611d3e9fe1c"}, + {file = "notebook-7.2.2.tar.gz", hash = "sha256:2ef07d4220421623ad3fe88118d687bc0450055570cdd160814a59cf3a1c516e"}, ] [package.dependencies] @@ -3596,57 +3630,57 @@ PyYAML = ">=5.1.0" [[package]] name = "opentelemetry-api" -version = "1.26.0" +version = "1.27.0" description = "OpenTelemetry Python API" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_api-1.26.0-py3-none-any.whl", hash = "sha256:7d7ea33adf2ceda2dd680b18b1677e4152000b37ca76e679da71ff103b943064"}, - {file = "opentelemetry_api-1.26.0.tar.gz", hash = "sha256:2bd639e4bed5b18486fef0b5a520aaffde5a18fc225e808a1ac4df363f43a1ce"}, + {file = "opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7"}, + {file = "opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.0.0" +importlib-metadata = ">=6.0,<=8.4.0" [[package]] name = "opentelemetry-exporter-otlp" -version = "1.26.0" +version = "1.27.0" description = "OpenTelemetry Collector Exporters" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp-1.26.0-py3-none-any.whl", hash = "sha256:f839989f54bda85ee33c5dae033c44dcec9ccbb0dafc6a43d585df44da1d2036"}, - {file = "opentelemetry_exporter_otlp-1.26.0.tar.gz", hash = "sha256:cf0e093f080011951d9f97431a83869761e4d4ebe83a4195ee92d7806223299c"}, + {file = "opentelemetry_exporter_otlp-1.27.0-py3-none-any.whl", hash = "sha256:7688791cbdd951d71eb6445951d1cfbb7b6b2d7ee5948fac805d404802931145"}, + {file = "opentelemetry_exporter_otlp-1.27.0.tar.gz", hash = "sha256:4a599459e623868cc95d933c301199c2367e530f089750e115599fccd67cb2a1"}, ] [package.dependencies] -opentelemetry-exporter-otlp-proto-grpc = "1.26.0" -opentelemetry-exporter-otlp-proto-http = "1.26.0" +opentelemetry-exporter-otlp-proto-grpc = "1.27.0" +opentelemetry-exporter-otlp-proto-http = "1.27.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.26.0" +version = "1.27.0" description = "OpenTelemetry Protobuf encoding" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.26.0-py3-none-any.whl", hash = "sha256:ee4d8f8891a1b9c372abf8d109409e5b81947cf66423fd998e56880057afbc71"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.26.0.tar.gz", hash = "sha256:bdbe50e2e22a1c71acaa0c8ba6efaadd58882e5a5978737a44a4c4b10d304c92"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.27.0-py3-none-any.whl", hash = "sha256:675db7fffcb60946f3a5c43e17d1168a3307a94a930ecf8d2ea1f286f3d4f79a"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.27.0.tar.gz", hash = "sha256:159d27cf49f359e3798c4c3eb8da6ef4020e292571bd8c5604a2a573231dd5c8"}, ] [package.dependencies] -opentelemetry-proto = "1.26.0" +opentelemetry-proto = "1.27.0" [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.26.0" +version = "1.27.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.26.0-py3-none-any.whl", hash = "sha256:e2be5eff72ebcb010675b818e8d7c2e7d61ec451755b8de67a140bc49b9b0280"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.26.0.tar.gz", hash = "sha256:a65b67a9a6b06ba1ec406114568e21afe88c1cdb29c464f2507d529eb906d8ae"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.27.0-py3-none-any.whl", hash = "sha256:56b5bbd5d61aab05e300d9d62a6b3c134827bbd28d0b12f2649c2da368006c9e"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.27.0.tar.gz", hash = "sha256:af6f72f76bcf425dfb5ad11c1a6d6eca2863b91e63575f89bb7b4b55099d968f"}, ] [package.dependencies] @@ -3654,39 +3688,39 @@ deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" grpcio = ">=1.0.0,<2.0.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.26.0" -opentelemetry-proto = "1.26.0" -opentelemetry-sdk = ">=1.26.0,<1.27.0" +opentelemetry-exporter-otlp-proto-common = "1.27.0" +opentelemetry-proto = "1.27.0" +opentelemetry-sdk = ">=1.27.0,<1.28.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.26.0" +version = "1.27.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.26.0-py3-none-any.whl", hash = "sha256:ee72a87c48ec977421b02f16c52ea8d884122470e0be573905237b540f4ee562"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.26.0.tar.gz", hash = "sha256:5801ebbcf7b527377883e6cbbdda35ee712dc55114fff1e93dfee210be56c908"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.27.0-py3-none-any.whl", hash = "sha256:688027575c9da42e179a69fe17e2d1eba9b14d81de8d13553a21d3114f3b4d75"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.27.0.tar.gz", hash = "sha256:2103479092d8eb18f61f3fbff084f67cc7f2d4a7d37e75304b8b56c1d09ebef5"}, ] [package.dependencies] deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.26.0" -opentelemetry-proto = "1.26.0" -opentelemetry-sdk = ">=1.26.0,<1.27.0" +opentelemetry-exporter-otlp-proto-common = "1.27.0" +opentelemetry-proto = "1.27.0" +opentelemetry-sdk = ">=1.27.0,<1.28.0" requests = ">=2.7,<3.0" [[package]] name = "opentelemetry-instrumentation" -version = "0.46b0" +version = "0.48b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_instrumentation-0.46b0-py3-none-any.whl", hash = "sha256:89cd721b9c18c014ca848ccd11181e6b3fd3f6c7669e35d59c48dc527408c18b"}, - {file = "opentelemetry_instrumentation-0.46b0.tar.gz", hash = "sha256:974e0888fb2a1e01c38fbacc9483d024bb1132aad92d6d24e2e5543887a7adda"}, + {file = "opentelemetry_instrumentation-0.48b0-py3-none-any.whl", hash = "sha256:a69750dc4ba6a5c3eb67986a337185a25b739966d80479befe37b546fc870b44"}, + {file = "opentelemetry_instrumentation-0.48b0.tar.gz", hash = "sha256:94929685d906380743a71c3970f76b5f07476eea1834abd5dd9d17abfe23cc35"}, ] [package.dependencies] @@ -3696,13 +3730,13 @@ wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-proto" -version = "1.26.0" +version = "1.27.0" description = "OpenTelemetry Python Proto" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_proto-1.26.0-py3-none-any.whl", hash = "sha256:6c4d7b4d4d9c88543bcf8c28ae3f8f0448a753dc291c18c5390444c90b76a725"}, - {file = "opentelemetry_proto-1.26.0.tar.gz", hash = "sha256:c5c18796c0cab3751fc3b98dee53855835e90c0422924b484432ac852d93dc1e"}, + {file = "opentelemetry_proto-1.27.0-py3-none-any.whl", hash = "sha256:b133873de5581a50063e1e4b29cdcf0c5e253a8c2d8dc1229add20a4c3830ace"}, + {file = "opentelemetry_proto-1.27.0.tar.gz", hash = "sha256:33c9345d91dafd8a74fc3d7576c5a38f18b7fdf8d02983ac67485386132aedd6"}, ] [package.dependencies] @@ -3710,34 +3744,34 @@ protobuf = ">=3.19,<5.0" [[package]] name = "opentelemetry-sdk" -version = "1.26.0" +version = "1.27.0" description = "OpenTelemetry Python SDK" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_sdk-1.26.0-py3-none-any.whl", hash = "sha256:feb5056a84a88670c041ea0ded9921fca559efec03905dddeb3885525e0af897"}, - {file = "opentelemetry_sdk-1.26.0.tar.gz", hash = "sha256:c90d2868f8805619535c05562d699e2f4fb1f00dbd55a86dcefca4da6fa02f85"}, + {file = "opentelemetry_sdk-1.27.0-py3-none-any.whl", hash = "sha256:365f5e32f920faf0fd9e14fdfd92c086e317eaa5f860edba9cdc17a380d9197d"}, + {file = "opentelemetry_sdk-1.27.0.tar.gz", hash = "sha256:d525017dea0ccce9ba4e0245100ec46ecdc043f2d7b8315d56b19aff0904fa6f"}, ] [package.dependencies] -opentelemetry-api = "1.26.0" -opentelemetry-semantic-conventions = "0.47b0" +opentelemetry-api = "1.27.0" +opentelemetry-semantic-conventions = "0.48b0" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.47b0" +version = "0.48b0" description = "OpenTelemetry Semantic Conventions" optional = true python-versions = ">=3.8" files = [ - {file = "opentelemetry_semantic_conventions-0.47b0-py3-none-any.whl", hash = "sha256:4ff9d595b85a59c1c1413f02bba320ce7ea6bf9e2ead2b0913c4395c7bbc1063"}, - {file = "opentelemetry_semantic_conventions-0.47b0.tar.gz", hash = "sha256:a8d57999bbe3495ffd4d510de26a97dadc1dace53e0275001b2c1b2f67992a7e"}, + {file = "opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl", hash = "sha256:a0de9f45c413a8669788a38569c7e0a11ce6ce97861a628cca785deecdc32a1f"}, + {file = "opentelemetry_semantic_conventions-0.48b0.tar.gz", hash = "sha256:12d74983783b6878162208be57c9effcb89dc88691c64992d70bb89dc00daa1a"}, ] [package.dependencies] deprecated = ">=1.2.6" -opentelemetry-api = "1.26.0" +opentelemetry-api = "1.27.0" [[package]] name = "overrides" @@ -3798,8 +3832,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -3867,18 +3901,18 @@ files = [ [[package]] name = "path" -version = "16.14.0" +version = "17.0.0" description = "A module wrapper for os.path" optional = false python-versions = ">=3.8" files = [ - {file = "path-16.14.0-py3-none-any.whl", hash = "sha256:8ee37703cbdc7cc83835ed4ecc6b638226fb2b43b7b45f26b620589981a109a5"}, - {file = "path-16.14.0.tar.gz", hash = "sha256:dbaaa7efd4602fd6ba8d82890dc7823d69e5de740a6e842d9919b0faaf2b6a8e"}, + {file = "path-17.0.0-py3-none-any.whl", hash = "sha256:b7309739c569e30110a34c6c812e582c09ff504c43e1232817410181838918ed"}, + {file = "path-17.0.0.tar.gz", hash = "sha256:e1540261d22df1416fb1b498b3b1ed5353a371a48fe197d66611bb01e7fab2d5"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["appdirs", "more-itertools", "packaging", "pygments", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "pywin32"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["appdirs", "more-itertools", "packaging", "pygments", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "pywin32"] [[package]] name = "path-py" @@ -3911,13 +3945,13 @@ files = [ [[package]] name = "pbr" -version = "6.0.0" +version = "6.1.0" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" files = [ - {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, - {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, + {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, + {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, ] [[package]] @@ -3947,84 +3981,95 @@ files = [ [[package]] name = "pillow" -version = "10.3.0" +version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, - {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, - {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, - {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, - {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, - {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, - {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, - {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, - {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, - {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, - {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, - {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, - {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, - {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, - {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, - {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, - {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -4058,13 +4103,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.1-py3-none-any.whl", hash = "sha256:facaa5a3c57aa1e053e3da7b49e0cc31fe0113ca42a4659d5c2e98e545624afe"}, + {file = "platformdirs-4.3.1.tar.gz", hash = "sha256:63b79589009fa8159973601dd4563143396b35c5f93a58b36f9049ff046949b1"}, ] [package.extras] @@ -4089,13 +4134,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poethepoet" -version = "0.27.0" +version = "0.28.0" description = "A task runner that works well with poetry." optional = false python-versions = ">=3.8" files = [ - {file = "poethepoet-0.27.0-py3-none-any.whl", hash = "sha256:0032d980a623b96e26dc7450ae200b0998be523f27d297d799b97510fe252a24"}, - {file = "poethepoet-0.27.0.tar.gz", hash = "sha256:907ab4dc1bc6326be5a3b10d2aa39d1acc0ca12024317d9506fbe9c0cdc912c9"}, + {file = "poethepoet-0.28.0-py3-none-any.whl", hash = "sha256:db6946ff39a1244235950cd720ee7182107f64126d3dcc64c9a996cc4d755404"}, + {file = "poethepoet-0.28.0.tar.gz", hash = "sha256:5dc3ee036ab0c93e918b5caed628274618b07d788e5cff6c4ae480913cbe009c"}, ] [package.dependencies] @@ -4199,22 +4244,22 @@ wcwidth = "*" [[package]] name = "protobuf" -version = "4.25.3" +version = "4.25.4" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, - {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, - {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, - {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, - {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, - {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, - {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, - {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, - {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, + {file = "protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4"}, + {file = "protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d"}, + {file = "protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b"}, + {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835"}, + {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040"}, + {file = "protobuf-4.25.4-cp38-cp38-win32.whl", hash = "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1"}, + {file = "protobuf-4.25.4-cp38-cp38-win_amd64.whl", hash = "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1"}, + {file = "protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca"}, + {file = "protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f"}, + {file = "protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978"}, + {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"}, ] [[package]] @@ -4259,13 +4304,13 @@ files = [ [[package]] name = "pure-eval" -version = "0.2.2" +version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" files = [ - {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, - {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, ] [package.extras] @@ -4283,52 +4328,55 @@ files = [ [[package]] name = "pyarrow" -version = "16.1.0" +version = "17.0.0" description = "Python library for Apache Arrow" optional = false python-versions = ">=3.8" files = [ - {file = "pyarrow-16.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:17e23b9a65a70cc733d8b738baa6ad3722298fa0c81d88f63ff94bf25eaa77b9"}, - {file = "pyarrow-16.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4740cc41e2ba5d641071d0ab5e9ef9b5e6e8c7611351a5cb7c1d175eaf43674a"}, - {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98100e0268d04e0eec47b73f20b39c45b4006f3c4233719c3848aa27a03c1aef"}, - {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68f409e7b283c085f2da014f9ef81e885d90dcd733bd648cfba3ef265961848"}, - {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a8914cd176f448e09746037b0c6b3a9d7688cef451ec5735094055116857580c"}, - {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:48be160782c0556156d91adbdd5a4a7e719f8d407cb46ae3bb4eaee09b3111bd"}, - {file = "pyarrow-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cf389d444b0f41d9fe1444b70650fea31e9d52cfcb5f818b7888b91b586efff"}, - {file = "pyarrow-16.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:d0ebea336b535b37eee9eee31761813086d33ed06de9ab6fc6aaa0bace7b250c"}, - {file = "pyarrow-16.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e73cfc4a99e796727919c5541c65bb88b973377501e39b9842ea71401ca6c1c"}, - {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9251264247ecfe93e5f5a0cd43b8ae834f1e61d1abca22da55b20c788417f6"}, - {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf5aace92d520d3d2a20031d8b0ec27b4395cab9f74e07cc95edf42a5cc0147"}, - {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:25233642583bf658f629eb230b9bb79d9af4d9f9229890b3c878699c82f7d11e"}, - {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a33a64576fddfbec0a44112eaf844c20853647ca833e9a647bfae0582b2ff94b"}, - {file = "pyarrow-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:185d121b50836379fe012753cf15c4ba9638bda9645183ab36246923875f8d1b"}, - {file = "pyarrow-16.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:2e51ca1d6ed7f2e9d5c3c83decf27b0d17bb207a7dea986e8dc3e24f80ff7d6f"}, - {file = "pyarrow-16.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06ebccb6f8cb7357de85f60d5da50e83507954af617d7b05f48af1621d331c9a"}, - {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04707f1979815f5e49824ce52d1dceb46e2f12909a48a6a753fe7cafbc44a0c"}, - {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d32000693deff8dc5df444b032b5985a48592c0697cb6e3071a5d59888714e2"}, - {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8785bb10d5d6fd5e15d718ee1d1f914fe768bf8b4d1e5e9bf253de8a26cb1628"}, - {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e1369af39587b794873b8a307cc6623a3b1194e69399af0efd05bb202195a5a7"}, - {file = "pyarrow-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:febde33305f1498f6df85e8020bca496d0e9ebf2093bab9e0f65e2b4ae2b3444"}, - {file = "pyarrow-16.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b5f5705ab977947a43ac83b52ade3b881eb6e95fcc02d76f501d549a210ba77f"}, - {file = "pyarrow-16.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d27bf89dfc2576f6206e9cd6cf7a107c9c06dc13d53bbc25b0bd4556f19cf5f"}, - {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d07de3ee730647a600037bc1d7b7994067ed64d0eba797ac74b2bc77384f4c2"}, - {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbef391b63f708e103df99fbaa3acf9f671d77a183a07546ba2f2c297b361e83"}, - {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19741c4dbbbc986d38856ee7ddfdd6a00fc3b0fc2d928795b95410d38bb97d15"}, - {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f2c5fb249caa17b94e2b9278b36a05ce03d3180e6da0c4c3b3ce5b2788f30eed"}, - {file = "pyarrow-16.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:e6b6d3cd35fbb93b70ade1336022cc1147b95ec6af7d36906ca7fe432eb09710"}, - {file = "pyarrow-16.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:18da9b76a36a954665ccca8aa6bd9f46c1145f79c0bb8f4f244f5f8e799bca55"}, - {file = "pyarrow-16.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99f7549779b6e434467d2aa43ab2b7224dd9e41bdde486020bae198978c9e05e"}, - {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f07fdffe4fd5b15f5ec15c8b64584868d063bc22b86b46c9695624ca3505b7b4"}, - {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfe389a08ea374972bd4065d5f25d14e36b43ebc22fc75f7b951f24378bf0b5"}, - {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b20bd67c94b3a2ea0a749d2a5712fc845a69cb5d52e78e6449bbd295611f3aa"}, - {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ba8ac20693c0bb0bf4b238751d4409e62852004a8cf031c73b0e0962b03e45e3"}, - {file = "pyarrow-16.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:31a1851751433d89a986616015841977e0a188662fcffd1a5677453f1df2de0a"}, - {file = "pyarrow-16.1.0.tar.gz", hash = "sha256:15fbb22ea96d11f0b5768504a3f961edab25eaf4197c341720c4a387f6c60315"}, + {file = "pyarrow-17.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a5c8b238d47e48812ee577ee20c9a2779e6a5904f1708ae240f53ecbee7c9f07"}, + {file = "pyarrow-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db023dc4c6cae1015de9e198d41250688383c3f9af8f565370ab2b4cb5f62655"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da1e060b3876faa11cee287839f9cc7cdc00649f475714b8680a05fd9071d545"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c06d4624c0ad6674364bb46ef38c3132768139ddec1c56582dbac54f2663e2"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:fa3c246cc58cb5a4a5cb407a18f193354ea47dd0648194e6265bd24177982fe8"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f7ae2de664e0b158d1607699a16a488de3d008ba99b3a7aa5de1cbc13574d047"}, + {file = "pyarrow-17.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5984f416552eea15fd9cee03da53542bf4cddaef5afecefb9aa8d1010c335087"}, + {file = "pyarrow-17.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:1c8856e2ef09eb87ecf937104aacfa0708f22dfeb039c363ec99735190ffb977"}, + {file = "pyarrow-17.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e19f569567efcbbd42084e87f948778eb371d308e137a0f97afe19bb860ccb3"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b244dc8e08a23b3e352899a006a26ae7b4d0da7bb636872fa8f5884e70acf15"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b72e87fe3e1db343995562f7fff8aee354b55ee83d13afba65400c178ab2597"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dc5c31c37409dfbc5d014047817cb4ccd8c1ea25d19576acf1a001fe07f5b420"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e3343cb1e88bc2ea605986d4b94948716edc7a8d14afd4e2c097232f729758b4"}, + {file = "pyarrow-17.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a27532c38f3de9eb3e90ecab63dfda948a8ca859a66e3a47f5f42d1e403c4d03"}, + {file = "pyarrow-17.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9b8a823cea605221e61f34859dcc03207e52e409ccf6354634143e23af7c8d22"}, + {file = "pyarrow-17.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1e70de6cb5790a50b01d2b686d54aaf73da01266850b05e3af2a1bc89e16053"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0071ce35788c6f9077ff9ecba4858108eebe2ea5a3f7cf2cf55ebc1dbc6ee24a"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:757074882f844411fcca735e39aae74248a1531367a7c80799b4266390ae51cc"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ba11c4f16976e89146781a83833df7f82077cdab7dc6232c897789343f7891a"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b0c6ac301093b42d34410b187bba560b17c0330f64907bfa4f7f7f2444b0cf9b"}, + {file = "pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7"}, + {file = "pyarrow-17.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:af5ff82a04b2171415f1410cff7ebb79861afc5dae50be73ce06d6e870615204"}, + {file = "pyarrow-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:edca18eaca89cd6382dfbcff3dd2d87633433043650c07375d095cd3517561d8"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c7916bff914ac5d4a8fe25b7a25e432ff921e72f6f2b7547d1e325c1ad9d155"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f553ca691b9e94b202ff741bdd40f6ccb70cdd5fbf65c187af132f1317de6145"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0cdb0e627c86c373205a2f94a510ac4376fdc523f8bb36beab2e7f204416163c"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:d7d192305d9d8bc9082d10f361fc70a73590a4c65cf31c3e6926cd72b76bc35c"}, + {file = "pyarrow-17.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:02dae06ce212d8b3244dd3e7d12d9c4d3046945a5933d28026598e9dbbda1fca"}, + {file = "pyarrow-17.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:13d7a460b412f31e4c0efa1148e1d29bdf18ad1411eb6757d38f8fbdcc8645fb"}, + {file = "pyarrow-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b564a51fbccfab5a04a80453e5ac6c9954a9c5ef2890d1bcf63741909c3f8df"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32503827abbc5aadedfa235f5ece8c4f8f8b0a3cf01066bc8d29de7539532687"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a155acc7f154b9ffcc85497509bcd0d43efb80d6f733b0dc3bb14e281f131c8b"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:dec8d129254d0188a49f8a1fc99e0560dc1b85f60af729f47de4046015f9b0a5"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:a48ddf5c3c6a6c505904545c25a4ae13646ae1f8ba703c4df4a1bfe4f4006bda"}, + {file = "pyarrow-17.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:42bf93249a083aca230ba7e2786c5f673507fa97bbd9725a1e2754715151a204"}, + {file = "pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28"}, ] [package.dependencies] numpy = ">=1.16.6" +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] + [[package]] name = "pyasn1" version = "0.6.0" @@ -4342,13 +4390,13 @@ files = [ [[package]] name = "pycodestyle" -version = "2.12.0" +version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, - {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] [[package]] @@ -4364,122 +4412,123 @@ files = [ [[package]] name = "pydantic" -version = "2.8.2" +version = "2.9.0" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, + {file = "pydantic-2.9.0-py3-none-any.whl", hash = "sha256:f66a7073abd93214a20c5f7b32d56843137a7a2e70d02111f3be287035c45370"}, + {file = "pydantic-2.9.0.tar.gz", hash = "sha256:c7a8a9fdf7d100afa49647eae340e2d23efa382466a8d177efcd1381e9be5598"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" +pydantic-core = "2.23.2" typing-extensions = [ {version = ">=4.6.1", markers = "python_version < \"3.13\""}, {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, ] +tzdata = {version = "*", markers = "python_version >= \"3.9\""} [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.20.1" +version = "2.23.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, + {file = "pydantic_core-2.23.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece"}, + {file = "pydantic_core-2.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc"}, + {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354"}, + {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2"}, + {file = "pydantic_core-2.23.2-cp310-none-win32.whl", hash = "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854"}, + {file = "pydantic_core-2.23.2-cp310-none-win_amd64.whl", hash = "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a"}, + {file = "pydantic_core-2.23.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8"}, + {file = "pydantic_core-2.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57"}, + {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4"}, + {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa"}, + {file = "pydantic_core-2.23.2-cp311-none-win32.whl", hash = "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576"}, + {file = "pydantic_core-2.23.2-cp311-none-win_amd64.whl", hash = "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589"}, + {file = "pydantic_core-2.23.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec"}, + {file = "pydantic_core-2.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0"}, + {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73"}, + {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0"}, + {file = "pydantic_core-2.23.2-cp312-none-win32.whl", hash = "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f"}, + {file = "pydantic_core-2.23.2-cp312-none-win_amd64.whl", hash = "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342"}, + {file = "pydantic_core-2.23.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac"}, + {file = "pydantic_core-2.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604"}, + {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d"}, + {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced"}, + {file = "pydantic_core-2.23.2-cp313-none-win32.whl", hash = "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1"}, + {file = "pydantic_core-2.23.2-cp313-none-win_amd64.whl", hash = "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac"}, + {file = "pydantic_core-2.23.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100"}, + {file = "pydantic_core-2.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f"}, + {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501"}, + {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5"}, + {file = "pydantic_core-2.23.2-cp38-none-win32.whl", hash = "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf"}, + {file = "pydantic_core-2.23.2-cp38-none-win_amd64.whl", hash = "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8"}, + {file = "pydantic_core-2.23.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59"}, + {file = "pydantic_core-2.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c"}, + {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80"}, + {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6"}, + {file = "pydantic_core-2.23.2-cp39-none-win32.whl", hash = "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437"}, + {file = "pydantic_core-2.23.2-cp39-none-win_amd64.whl", hash = "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2"}, + {file = "pydantic_core-2.23.2.tar.gz", hash = "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd"}, ] [package.dependencies] @@ -4677,17 +4726,17 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.23.7" +version = "0.24.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, - {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, ] [package.dependencies] -pytest = ">=7.0.0,<9" +pytest = ">=8.2,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] @@ -4812,40 +4861,41 @@ files = [ [[package]] name = "python-on-whales" -version = "0.72.0" +version = "0.73.0" description = "A Docker client for Python, designed to be fun and intuitive!" optional = false python-versions = "<4,>=3.8" files = [ - {file = "python_on_whales-0.72.0-py3-none-any.whl", hash = "sha256:092b440f43a34bbe90536b82a41ae3c199018dfc66d7e0dc52d4d659b82683c2"}, - {file = "python_on_whales-0.72.0.tar.gz", hash = "sha256:280da91724ae728ac2baf503584938ecfdee2f83bb88e65413eed14b1fe74d41"}, + {file = "python_on_whales-0.73.0-py3-none-any.whl", hash = "sha256:66f31749c2544a0aacb4e3ba03772c2e9227235ea1aecd58aa7a4cdcf26f559a"}, + {file = "python_on_whales-0.73.0.tar.gz", hash = "sha256:c76bf3633550e5c948fb4215918364f45efaddb2e09df5ddd169132f7ffdc249"}, ] [package.dependencies] -pydantic = ">=1.9,<2.0.dev0 || >=2.1.dev0,<3" +pydantic = ">=2.1.dev0,<3" requests = "*" tqdm = "*" typer = ">=0.4.1" typing-extensions = "*" [package.extras] +dev = ["ruff (==0.5.6)"] test = ["pytest"] [[package]] name = "python-telegram-bot" -version = "21.4" +version = "21.5" description = "We have made you a wrapper you can't refuse" optional = true python-versions = ">=3.8" files = [ - {file = "python_telegram_bot-21.4-py3-none-any.whl", hash = "sha256:71ce864130af43b48be70ff0593ef4db31d99d92aa64a2933289b8d544e6e9df"}, - {file = "python_telegram_bot-21.4.tar.gz", hash = "sha256:d4d41f29f2e2c02920a4be75d9c1ecf85ec381bf47bbeb51af5b097e340b8377"}, + {file = "python_telegram_bot-21.5-py3-none-any.whl", hash = "sha256:1bbba653477ba164411622b717a0cfe1eb7843da016348e41df97f96c93f578e"}, + {file = "python_telegram_bot-21.5.tar.gz", hash = "sha256:2d679173072cce8d6b49aac2e438d49dbfc01c1a4ef5658828c2a65951ee830b"}, ] [package.dependencies] aiolimiter = {version = ">=1.1.0,<1.2.0", optional = true, markers = "extra == \"all\""} apscheduler = {version = ">=3.10.4,<3.11.0", optional = true, markers = "extra == \"all\""} -cachetools = {version = ">=5.3.3,<5.4.0", optional = true, markers = "extra == \"all\""} +cachetools = {version = ">=5.3.3,<5.6.0", optional = true, markers = "extra == \"all\""} cffi = {version = ">=1.17.0rc1", optional = true, markers = "python_version > \"3.12\" and extra == \"all\""} cryptography = {version = ">=39.0.1", optional = true, markers = "extra == \"all\""} httpx = [ @@ -4857,9 +4907,9 @@ pytz = {version = ">=2018.6", optional = true, markers = "extra == \"all\""} tornado = {version = ">=6.4,<7.0", optional = true, markers = "extra == \"all\""} [package.extras] -all = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.4.0)", "cffi (>=1.17.0rc1)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] -callback-data = ["cachetools (>=5.3.3,<5.4.0)"] -ext = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.4.0)", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +all = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.6.0)", "cffi (>=1.17.0rc1)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +callback-data = ["cachetools (>=5.3.3,<5.6.0)"] +ext = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.6.0)", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] http2 = ["httpx[http2]"] job-queue = ["apscheduler (>=3.10.4,<3.11.0)", "pytz (>=2018.6)"] passport = ["cffi (>=1.17.0rc1)", "cryptography (>=39.0.1)"] @@ -4903,13 +4953,13 @@ files = [ [[package]] name = "pywin32-ctypes" -version = "0.2.2" +version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" files = [ - {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, - {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, ] [[package]] @@ -4929,305 +4979,302 @@ files = [ [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "pyzmq" -version = "26.0.3" +version = "26.2.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.7" files = [ - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, - {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, - {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, - {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, - {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, - {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, - {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, - {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"}, + {file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"}, + {file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"}, + {file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"}, + {file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"}, + {file = "pyzmq-26.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win32.whl", hash = "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd"}, + {file = "pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988"}, + {file = "pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940"}, + {file = "pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f"}, + {file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"}, ] [package.dependencies] cffi = {version = "*", markers = "implementation_name == \"pypy\""} -[[package]] -name = "qtconsole" -version = "5.5.2" -description = "Jupyter Qt console" -optional = false -python-versions = ">=3.8" -files = [ - {file = "qtconsole-5.5.2-py3-none-any.whl", hash = "sha256:42d745f3d05d36240244a04e1e1ec2a86d5d9b6edb16dbdef582ccb629e87e0b"}, - {file = "qtconsole-5.5.2.tar.gz", hash = "sha256:6b5fb11274b297463706af84dcbbd5c92273b1f619e6d25d08874b0a88516989"}, -] - -[package.dependencies] -ipykernel = ">=4.1" -jupyter-client = ">=4.1" -jupyter-core = "*" -packaging = "*" -pygments = "*" -pyzmq = ">=17.1" -qtpy = ">=2.4.0" -traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2" - -[package.extras] -doc = ["Sphinx (>=1.3)"] -test = ["flaky", "pytest", "pytest-qt"] - -[[package]] -name = "qtpy" -version = "2.4.1" -description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." -optional = false -python-versions = ">=3.7" -files = [ - {file = "QtPy-2.4.1-py3-none-any.whl", hash = "sha256:1c1d8c4fa2c884ae742b069151b0abe15b3f70491f3972698c683b8e38de839b"}, - {file = "QtPy-2.4.1.tar.gz", hash = "sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987"}, -] - -[package.dependencies] -packaging = "*" - -[package.extras] -test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] - [[package]] name = "rapidfuzz" -version = "3.9.3" +version = "3.9.7" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.8" files = [ - {file = "rapidfuzz-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdb8c5b8e29238ec80727c2ba3b301efd45aa30c6a7001123a6647b8e6f77ea4"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3bd0d9632088c63a241f217742b1cf86e2e8ae573e01354775bd5016d12138c"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:153f23c03d4917f6a1fc2fb56d279cc6537d1929237ff08ee7429d0e40464a18"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96c5225e840f1587f1bac8fa6f67562b38e095341576e82b728a82021f26d62"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b777cd910ceecd738adc58593d6ed42e73f60ad04ecdb4a841ae410b51c92e0e"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53e06e4b81f552da04940aa41fc556ba39dee5513d1861144300c36c33265b76"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c7ca5b6050f18fdcacdada2dc5fb7619ff998cd9aba82aed2414eee74ebe6cd"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:87bb8d84cb41446a808c4b5f746e29d8a53499381ed72f6c4e456fe0f81c80a8"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:959a15186d18425d19811bea86a8ffbe19fd48644004d29008e636631420a9b7"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a24603dd05fb4e3c09d636b881ce347e5f55f925a6b1b4115527308a323b9f8e"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d055da0e801c71dd74ba81d72d41b2fa32afa182b9fea6b4b199d2ce937450d"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:875b581afb29a7213cf9d98cb0f98df862f1020bce9d9b2e6199b60e78a41d14"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-win32.whl", hash = "sha256:6073a46f61479a89802e3f04655267caa6c14eb8ac9d81a635a13805f735ebc1"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:119c010e20e561249b99ca2627f769fdc8305b07193f63dbc07bca0a6c27e892"}, - {file = "rapidfuzz-3.9.3-cp310-cp310-win_arm64.whl", hash = "sha256:790b0b244f3213581d42baa2fed8875f9ee2b2f9b91f94f100ec80d15b140ba9"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f57e8305c281e8c8bc720515540e0580355100c0a7a541105c6cafc5de71daae"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a4fc7b784cf987dbddc300cef70e09a92ed1bce136f7bb723ea79d7e297fe76d"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b422c0a6fe139d5447a0766268e68e6a2a8c2611519f894b1f31f0a392b9167"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f50fed4a9b0c9825ff37cf0bccafd51ff5792090618f7846a7650f21f85579c9"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b80eb7cbe62348c61d3e67e17057cddfd6defab168863028146e07d5a8b24a89"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f45be77ec82da32ce5709a362e236ccf801615cc7163b136d1778cf9e31b14"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd84b7f652a5610733400307dc732f57c4a907080bef9520412e6d9b55bc9adc"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e6d27dad8c990218b8cd4a5c99cbc8834f82bb46ab965a7265d5aa69fc7ced7"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:05ee0696ebf0dfe8f7c17f364d70617616afc7dafe366532730ca34056065b8a"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2bc8391749e5022cd9e514ede5316f86e332ffd3cfceeabdc0b17b7e45198a8c"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:93981895602cf5944d89d317ae3b1b4cc684d175a8ae2a80ce5b65615e72ddd0"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:754b719a4990735f66653c9e9261dcf52fd4d925597e43d6b9069afcae700d21"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-win32.whl", hash = "sha256:14c9f268ade4c88cf77ab007ad0fdf63699af071ee69378de89fff7aa3cae134"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc1991b4cde6c9d3c0bbcb83d5581dc7621bec8c666c095c65b4277233265a82"}, - {file = "rapidfuzz-3.9.3-cp311-cp311-win_arm64.whl", hash = "sha256:0c34139df09a61b1b557ab65782ada971b4a3bce7081d1b2bee45b0a52231adb"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d6a210347d6e71234af5c76d55eeb0348b026c9bb98fe7c1cca89bac50fb734"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b300708c917ce52f6075bdc6e05b07c51a085733650f14b732c087dc26e0aaad"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83ea7ca577d76778250421de61fb55a719e45b841deb769351fc2b1740763050"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8319838fb5b7b5f088d12187d91d152b9386ce3979ed7660daa0ed1bff953791"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:505d99131afd21529293a9a7b91dfc661b7e889680b95534756134dc1cc2cd86"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c52970f7784518d7c82b07a62a26e345d2de8c2bd8ed4774e13342e4b3ff4200"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:143caf7247449055ecc3c1e874b69e42f403dfc049fc2f3d5f70e1daf21c1318"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b8ab0fa653d9225195a8ff924f992f4249c1e6fa0aea563f685e71b81b9fcccf"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57e7c5bf7b61c7320cfa5dde1e60e678d954ede9bb7da8e763959b2138391401"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:51fa1ba84653ab480a2e2044e2277bd7f0123d6693051729755addc0d015c44f"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:17ff7f7eecdb169f9236e3b872c96dbbaf116f7787f4d490abd34b0116e3e9c8"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afe7c72d3f917b066257f7ff48562e5d462d865a25fbcabf40fca303a9fa8d35"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-win32.whl", hash = "sha256:e53ed2e9b32674ce96eed80b3b572db9fd87aae6742941fb8e4705e541d861ce"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:35b7286f177e4d8ba1e48b03612f928a3c4bdac78e5651379cec59f95d8651e6"}, - {file = "rapidfuzz-3.9.3-cp312-cp312-win_arm64.whl", hash = "sha256:e6e4b9380ed4758d0cb578b0d1970c3f32dd9e87119378729a5340cb3169f879"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a39890013f6d5b056cc4bfdedc093e322462ece1027a57ef0c636537bdde7531"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b5bc0fdbf419493163c5c9cb147c5fbe95b8e25844a74a8807dcb1a125e630cf"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe6e200a75a792d37b960457904c4fce7c928a96ae9e5d21d2bd382fe39066e"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de077c468c225d4c18f7188c47d955a16d65f21aab121cbdd98e3e2011002c37"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f917eaadf5388466a95f6a236f678a1588d231e52eda85374077101842e794e"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858ba57c05afd720db8088a8707079e8d024afe4644001fe0dbd26ef7ca74a65"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d36447d21b05f90282a6f98c5a33771805f9222e5d0441d03eb8824e33e5bbb4"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:acbe4b6f1ccd5b90c29d428e849aa4242e51bb6cab0448d5f3c022eb9a25f7b1"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:53c7f27cdf899e94712972237bda48cfd427646aa6f5d939bf45d084780e4c16"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:6175682a829c6dea4d35ed707f1dadc16513270ef64436568d03b81ccb6bdb74"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5276df395bd8497397197fca2b5c85f052d2e6a66ffc3eb0544dd9664d661f95"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:77b5c4f3e72924d7845f0e189c304270066d0f49635cf8a3938e122c437e58de"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-win32.whl", hash = "sha256:8add34061e5cd561c72ed4febb5c15969e7b25bda2bb5102d02afc3abc1f52d0"}, - {file = "rapidfuzz-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:604e0502a39cf8e67fa9ad239394dddad4cdef6d7008fdb037553817d420e108"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21047f55d674614eb4b0ab34e35c3dc66f36403b9fbfae645199c4a19d4ed447"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a56da3aff97cb56fe85d9ca957d1f55dbac7c27da927a86a2a86d8a7e17f80aa"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c08481aec2fe574f0062e342924db2c6b321391aeb73d68853ed42420fd6d"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e2b827258beefbe5d3f958243caa5a44cf46187eff0c20e0b2ab62d1550327a"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6e65a301fcd19fbfbee3a514cc0014ff3f3b254b9fd65886e8a9d6957fb7bca"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe93ba1725a8d47d2b9dca6c1f435174859427fbc054d83de52aea5adc65729"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca21c0a34adee582775da997a600283e012a608a107398d80a42f9a57ad323d"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:256e07d3465173b2a91c35715a2277b1ee3ae0b9bbab4e519df6af78570741d0"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:802ca2cc8aa6b8b34c6fdafb9e32540c1ba05fca7ad60b3bbd7ec89ed1797a87"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:dd789100fc852cffac1449f82af0da139d36d84fd9faa4f79fc4140a88778343"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:5d0abbacdb06e27ff803d7ae0bd0624020096802758068ebdcab9bd49cf53115"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:378d1744828e27490a823fc6fe6ebfb98c15228d54826bf4e49e4b76eb5f5579"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-win32.whl", hash = "sha256:5d0cb272d43e6d3c0dedefdcd9d00007471f77b52d2787a4695e9dd319bb39d2"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:15e4158ac4b3fb58108072ec35b8a69165f651ba1c8f43559a36d518dbf9fb3f"}, - {file = "rapidfuzz-3.9.3-cp39-cp39-win_arm64.whl", hash = "sha256:58c6a4936190c558d5626b79fc9e16497e5df7098589a7e80d8bff68148ff096"}, - {file = "rapidfuzz-3.9.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5410dc848c947a603792f4f51b904a3331cf1dc60621586bfbe7a6de72da1091"}, - {file = "rapidfuzz-3.9.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:282d55700a1a3d3a7980746eb2fcd48c9bbc1572ebe0840d0340d548a54d01fe"}, - {file = "rapidfuzz-3.9.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc1037507810833646481f5729901a154523f98cbebb1157ba3a821012e16402"}, - {file = "rapidfuzz-3.9.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e33f779391caedcba2ba3089fb6e8e557feab540e9149a5c3f7fea7a3a7df37"}, - {file = "rapidfuzz-3.9.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41a81a9f311dc83d22661f9b1a1de983b201322df0c4554042ffffd0f2040c37"}, - {file = "rapidfuzz-3.9.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a93250bd8fae996350c251e1752f2c03335bb8a0a5b0c7e910a593849121a435"}, - {file = "rapidfuzz-3.9.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3617d1aa7716c57d120b6adc8f7c989f2d65bc2b0cbd5f9288f1fc7bf469da11"}, - {file = "rapidfuzz-3.9.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad04a3f5384b82933213bba2459f6424decc2823df40098920856bdee5fd6e88"}, - {file = "rapidfuzz-3.9.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8709918da8a88ad73c9d4dd0ecf24179a4f0ceba0bee21efc6ea21a8b5290349"}, - {file = "rapidfuzz-3.9.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b770f85eab24034e6ef7df04b2bfd9a45048e24f8a808e903441aa5abde8ecdd"}, - {file = "rapidfuzz-3.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930b4e6fdb4d914390141a2b99a6f77a52beacf1d06aa4e170cba3a98e24c1bc"}, - {file = "rapidfuzz-3.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c8444e921bfc3757c475c4f4d7416a7aa69b2d992d5114fe55af21411187ab0d"}, - {file = "rapidfuzz-3.9.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c1d3ef3878f871abe6826e386c3d61b5292ef5f7946fe646f4206b85836b5da"}, - {file = "rapidfuzz-3.9.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d861bf326ee7dabc35c532a40384541578cd1ec1e1b7db9f9ecbba56eb76ca22"}, - {file = "rapidfuzz-3.9.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cde6b9d9ba5007077ee321ec722fa714ebc0cbd9a32ccf0f4dd3cc3f20952d71"}, - {file = "rapidfuzz-3.9.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb6546e7b6bed1aefbe24f68a5fb9b891cc5aef61bca6c1a7b1054b7f0359bb"}, - {file = "rapidfuzz-3.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d8a57261ef7996d5ced7c8cba9189ada3fbeffd1815f70f635e4558d93766cb"}, - {file = "rapidfuzz-3.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:67201c02efc596923ad950519e0b75ceb78d524177ea557134d6567b9ac2c283"}, - {file = "rapidfuzz-3.9.3.tar.gz", hash = "sha256:b398ea66e8ed50451bce5997c430197d5e4b06ac4aa74602717f792d8d8d06e2"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccf68e30b80e903f2309f90a438dbd640dd98e878eeb5ad361a288051ee5b75c"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:696a79018ef989bf1c9abd9005841cee18005ccad4748bad8a4c274c47b6241a"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4eebf6c93af0ae866c22b403a84747580bb5c10f0d7b51c82a87f25405d4dcb"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e9125377fa3d21a8abd4fbdbcf1c27be73e8b1850f0b61b5b711364bf3b59db"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c12d180b17a22d107c8747de9c68d0b9c1d15dcda5445ff9bf9f4ccfb67c3e16"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1318d42610c26dcd68bd3279a1bf9e3605377260867c9a8ed22eafc1bd93a7c"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5fa6e3c6e0333051c1f3a49f0807b3366f4131c8d6ac8c3e05fd0d0ce3755c"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fcf79b686962d7bec458a0babc904cb4fa319808805e036b9d5a531ee6b9b835"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8b01153c7466d0bad48fba77a303d5a768e66f24b763853469f47220b3de4661"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:94baaeea0b4f8632a6da69348b1e741043eba18d4e3088d674d3f76586b6223d"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6c5b32875646cb7f60c193ade99b2e4b124f19583492115293cd00f6fb198b17"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:110b6294396bc0a447648627479c9320f095c2034c0537f687592e0f58622638"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-win32.whl", hash = "sha256:3445a35c4c8d288f2b2011eb61bce1227c633ce85a3154e727170f37c0266bb2"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-win_amd64.whl", hash = "sha256:0d1415a732ee75e74a90af12020b77a0b396b36c60afae1bde3208a78cd2c9fc"}, + {file = "rapidfuzz-3.9.7-cp310-cp310-win_arm64.whl", hash = "sha256:836f4d88b8bd0fff2ebe815dcaab8aa6c8d07d1d566a7e21dd137cf6fe11ed5b"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d098ce6162eb5e48fceb0745455bc950af059df6113eec83e916c129fca11408"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:048d55d36c02c6685a2b2741688503c3d15149694506655b6169dcfd3b6c2585"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c33211cfff9aec425bb1bfedaf94afcf337063aa273754f22779d6dadebef4c2"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6d9db2fa4e9be171e9bb31cf2d2575574774966b43f5b951062bb2e67885852"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4e049d5ad61448c9a020d1061eba20944c4887d720c4069724beb6ea1692507"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cfa74aac64c85898b93d9c80bb935a96bf64985e28d4ee0f1a3d1f3bf11a5106"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965693c2e9efd425b0f059f5be50ef830129f82892fa1858e220e424d9d0160f"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8501000a5eb8037c4b56857724797fe5a8b01853c363de91c8d0d0ad56bef319"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d92c552c6b7577402afdd547dcf5d31ea6c8ae31ad03f78226e055cfa37f3c6"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1ee2086f490cb501d86b7e386c1eb4e3a0ccbb0c99067089efaa8c79012c8952"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1de91e7fd7f525e10ea79a6e62c559d1b0278ec097ad83d9da378b6fab65a265"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4da514d13f4433e16960a17f05b67e0af30ac771719c9a9fb877e5004f74477"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-win32.whl", hash = "sha256:a40184c67db8252593ec518e17fb8a6e86d7259dc9f2d6c0bf4ff4db8cf1ad4b"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-win_amd64.whl", hash = "sha256:c4f28f1930b09a2c300357d8465b388cecb7e8b2f454a5d5425561710b7fd07f"}, + {file = "rapidfuzz-3.9.7-cp311-cp311-win_arm64.whl", hash = "sha256:675b75412a943bb83f1f53e2e54fd18c80ef15ed642dc6eb0382d1949419d904"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1ef6a1a8f0b12f8722f595f15c62950c9a02d5abc64742561299ffd49f6c6944"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:32532af1d70c6ec02ea5ac7ee2766dfff7c8ae8c761abfe8da9e527314e634e8"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1a38bade755aa9dd95a81cda949e1bf9cd92b79341ccc5e2189c9e7bdfc5ec"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d73ee2df41224c87336448d279b5b6a3a75f36e41dd3dcf538c0c9cce36360d8"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be3a1fc3e2ab3bdf93dc0c83c00acca8afd2a80602297d96cf4a0ba028333cdf"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603f48f621272a448ff58bb556feb4371252a02156593303391f5c3281dfaeac"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:268f8e1ca50fc61c0736f3fe9d47891424adf62d96ed30196f30f4bd8216b41f"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f8bf3f0d02935751d8660abda6044821a861f6229f7d359f98bcdcc7e66c39b"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b997ff3b39d4cee9fb025d6c46b0a24bd67595ce5a5b652a97fb3a9d60beb651"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca66676c8ef6557f9b81c5b2b519097817a7c776a6599b8d6fcc3e16edd216fe"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:35d3044cb635ca6b1b2b7b67b3597bd19f34f1753b129eb6d2ae04cf98cd3945"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a93c9e60904cb76e7aefef67afffb8b37c4894f81415ed513db090f29d01101"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-win32.whl", hash = "sha256:579d107102c0725f7c79b4e79f16d3cf4d7c9208f29c66b064fa1fd4641d5155"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-win_amd64.whl", hash = "sha256:953b3780765c8846866faf891ee4290f6a41a6dacf4fbcd3926f78c9de412ca6"}, + {file = "rapidfuzz-3.9.7-cp312-cp312-win_arm64.whl", hash = "sha256:7c20c1474b068c4bd45bf2fd0ad548df284f74e9a14a68b06746c56e3aa8eb70"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fde81b1da9a947f931711febe2e2bee694e891f6d3e6aa6bc02c1884702aea19"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47e92c155a14f44511ea8ebcc6bc1535a1fe8d0a7d67ad3cc47ba61606df7bcf"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8772b745668260c5c4d069c678bbaa68812e6c69830f3771eaad521af7bc17f8"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578302828dd97ee2ba507d2f71d62164e28d2fc7bc73aad0d2d1d2afc021a5d5"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc3e6081069eea61593f1d6839029da53d00c8c9b205c5534853eaa3f031085c"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b1c2d504eddf97bc0f2eba422c8915576dbf025062ceaca2d68aecd66324ad9"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb76e5a21034f0307c51c5a2fc08856f698c53a4c593b17d291f7d6e9d09ca3"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d4ba2318ef670ce505f42881a5d2af70f948124646947341a3c6ccb33cd70369"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:057bb03f39e285047d7e9412e01ecf31bb2d42b9466a5409d715d587460dd59b"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a8feac9006d5c9758438906f093befffc4290de75663dbb2098461df7c7d28dd"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95b8292383e717e10455f2c917df45032b611141e43d1adf70f71b1566136b11"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e9fbf659537d246086d0297628b3795dc3e4a384101ecc01e5791c827b8d7345"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-win32.whl", hash = "sha256:1dc516ac6d32027be2b0196bedf6d977ac26debd09ca182376322ad620460feb"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-win_amd64.whl", hash = "sha256:b4f86e09d3064dca0b014cd48688964036a904a2d28048f00c8f4640796d06a8"}, + {file = "rapidfuzz-3.9.7-cp313-cp313-win_arm64.whl", hash = "sha256:19c64d8ddb2940b42a4567b23f1681af77f50a5ff6c9b8e85daba079c210716e"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbda3dd68d8b28ccb20ffb6f756fefd9b5ba570a772bedd7643ed441f5793308"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2379e0b2578ad3ac7004f223251550f08bca873ff76c169b09410ec562ad78d8"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d1eff95362f993b0276fd3839aee48625b09aac8938bb0c23b40d219cba5dc5"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd9360e30041690912525a210e48a897b49b230768cc8af1c702e5395690464f"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a93cd834b3c315ab437f0565ee3a2f42dd33768dc885ccbabf9710b131cf70d2"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff196996240db7075f62c7bc4506f40a3c80cd4ae3ab0e79ac6892283a90859"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948dcee7aaa1cd14358b2a7ef08bf0be42bf89049c3a906669874a715fc2c937"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95751f505a301af1aaf086c19f34536056d6c8efa91b2240de532a3db57b543"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:90db86fa196eecf96cb6db09f1083912ea945c50c57188039392d810d0b784e1"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:3171653212218a162540a3c8eb8ae7d3dcc8548540b69eaecaf3b47c14d89c90"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:36dd6e820379c37a1ffefc8a52b648758e867cd9d78ee5b5dc0c9a6a10145378"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:7b702de95666a1f7d5c6b47eacadfe2d2794af3742d63d2134767d13e5d1c713"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-win32.whl", hash = "sha256:9030e7238c0df51aed5c9c5ed8eee2bdd47a2ae788e562c1454af2851c3d1906"}, + {file = "rapidfuzz-3.9.7-cp38-cp38-win_amd64.whl", hash = "sha256:f847fb0fbfb72482b1c05c59cbb275c58a55b73708a7f77a83f8035ee3c86497"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97f2ce529d2a70a60c290f6ab269a2bbf1d3b47b9724dccc84339b85f7afb044"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e2957fdad10bb83b1982b02deb3604a3f6911a5e545f518b59c741086f92d152"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d5262383634626eb45c536017204b8163a03bc43bda880cf1bdd7885db9a163"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:364587827d7cbd41afa0782adc2d2d19e3f07d355b0750a02a8e33ad27a9c368"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecc24af7f905f3d6efb371a01680116ffea8d64e266618fb9ad1602a9b4f7934"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dc86aa6b29d174713c5f4caac35ffb7f232e3e649113e8d13812b35ab078228"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3dcfbe7266e74a707173a12a7b355a531f2dcfbdb32f09468e664330da14874"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b23806fbdd6b510ba9ac93bb72d503066263b0fba44b71b835be9f063a84025f"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5551d68264c1bb6943f542da83a4dc8940ede52c5847ef158698799cc28d14f5"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:13d8675a1fa7e2b19650ca7ef9a6ec01391d4bb12ab9e0793e8eb024538b4a34"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9b6a5de507b9be6de688dae40143b656f7a93b10995fb8bd90deb555e7875c60"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:111a20a3c090cf244d9406e60500b6c34b2375ba3a5009e2b38fd806fe38e337"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-win32.whl", hash = "sha256:22589c0b8ccc6c391ce7f776c93a8c92c96ab8d34e1a19f1bd2b12a235332632"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-win_amd64.whl", hash = "sha256:6f83221db5755b8f34222e40607d87f1176a8d5d4dbda4a55a0f0b67d588a69c"}, + {file = "rapidfuzz-3.9.7-cp39-cp39-win_arm64.whl", hash = "sha256:3665b92e788578c3bb334bd5b5fa7ee1a84bafd68be438e3110861d1578c63a0"}, + {file = "rapidfuzz-3.9.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7df9c2194c7ec930b33c991c55dbd0c10951bd25800c0b7a7b571994ebbced5"}, + {file = "rapidfuzz-3.9.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:68bd888eafd07b09585dcc8bc2716c5ecdb7eed62827470664d25588982b2873"}, + {file = "rapidfuzz-3.9.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1230e0f9026851a6a432beaa0ce575dda7b39fe689b576f99a0704fbb81fc9c"}, + {file = "rapidfuzz-3.9.7-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b36e1c61b796ae1777f3e9e11fd39898b09d351c9384baf6e3b7e6191d8ced"}, + {file = "rapidfuzz-3.9.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dba13d86806fcf3fe9c9919f58575e0090eadfb89c058bde02bcc7ab24e4548"}, + {file = "rapidfuzz-3.9.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1f1a33e84056b7892c721d84475d3bde49a145126bc4c6efe0d6d0d59cb31c29"}, + {file = "rapidfuzz-3.9.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3492c7a42b7fa9f0051d7fcce9893e95ed91c97c9ec7fb64346f3e070dd318ed"}, + {file = "rapidfuzz-3.9.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:ece45eb2af8b00f90d10f7419322e8804bd42fb1129026f9bfe712c37508b514"}, + {file = "rapidfuzz-3.9.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcd14cf4876f04b488f6e54a7abd3e9b31db5f5a6aba0ce90659917aaa8c088"}, + {file = "rapidfuzz-3.9.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:521c58c72ed8a612b25cda378ff10dee17e6deb4ee99a070b723519a345527b9"}, + {file = "rapidfuzz-3.9.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18669bb6cdf7d40738526d37e550df09ba065b5a7560f3d802287988b6cb63cf"}, + {file = "rapidfuzz-3.9.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7abe2dbae81120a64bb4f8d3fcafe9122f328c9f86d7f327f174187a5af4ed86"}, + {file = "rapidfuzz-3.9.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a3c0783910911f4f24655826d007c9f4360f08107410952c01ee3df98c713eb2"}, + {file = "rapidfuzz-3.9.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:03126f9a040ff21d2a110610bfd6b93b79377ce8b4121edcb791d61b7df6eec5"}, + {file = "rapidfuzz-3.9.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:591908240f4085e2ade5b685c6e8346e2ed44932cffeaac2fb32ddac95b55c7f"}, + {file = "rapidfuzz-3.9.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9012d86c6397edbc9da4ac0132de7f8ee9d6ce857f4194d5684c4ddbcdd1c5c"}, + {file = "rapidfuzz-3.9.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df596ddd3db38aa513d4c0995611267b3946e7cbe5a8761b50e9306dfec720ee"}, + {file = "rapidfuzz-3.9.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3ed5adb752f4308fcc8f4fb6f8eb7aa4082f9d12676fda0a74fa5564242a8107"}, + {file = "rapidfuzz-3.9.7.tar.gz", hash = "sha256:f1c7296534c1afb6f495aa95871f14ccdc197c6db42965854e483100df313030"}, ] [package.extras] @@ -5235,20 +5282,20 @@ full = ["numpy"] [[package]] name = "redis" -version = "5.0.7" +version = "5.0.8" description = "Python client for Redis database and key-value store" optional = true python-versions = ">=3.7" files = [ - {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, - {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, + {file = "redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4"}, + {file = "redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870"}, ] [package.dependencies] async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} [package.extras] -hiredis = ["hiredis (>=1.0.0)"] +hiredis = ["hiredis (>1.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] [[package]] @@ -5328,13 +5375,13 @@ files = [ [[package]] name = "rich" -version = "13.7.1" +version = "13.8.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, + {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, ] [package.dependencies] @@ -5357,110 +5404,114 @@ files = [ [[package]] name = "rpds-py" -version = "0.18.1" +version = "0.20.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53"}, - {file = "rpds_py-0.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d"}, - {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60"}, - {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da"}, - {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1"}, - {file = "rpds_py-0.18.1-cp310-none-win32.whl", hash = "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333"}, - {file = "rpds_py-0.18.1-cp310-none-win_amd64.whl", hash = "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a"}, - {file = "rpds_py-0.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8"}, - {file = "rpds_py-0.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8"}, - {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7"}, - {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e"}, - {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88"}, - {file = "rpds_py-0.18.1-cp311-none-win32.whl", hash = "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb"}, - {file = "rpds_py-0.18.1-cp311-none-win_amd64.whl", hash = "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2"}, - {file = "rpds_py-0.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3"}, - {file = "rpds_py-0.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac"}, - {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c"}, - {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac"}, - {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a"}, - {file = "rpds_py-0.18.1-cp312-none-win32.whl", hash = "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6"}, - {file = "rpds_py-0.18.1-cp312-none-win_amd64.whl", hash = "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72"}, - {file = "rpds_py-0.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74"}, - {file = "rpds_py-0.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0"}, - {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d"}, - {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e"}, - {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc"}, - {file = "rpds_py-0.18.1-cp38-none-win32.whl", hash = "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9"}, - {file = "rpds_py-0.18.1-cp38-none-win_amd64.whl", hash = "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2"}, - {file = "rpds_py-0.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93"}, - {file = "rpds_py-0.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c"}, - {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338"}, - {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b"}, - {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26"}, - {file = "rpds_py-0.18.1-cp39-none-win32.whl", hash = "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360"}, - {file = "rpds_py-0.18.1-cp39-none-win_amd64.whl", hash = "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e"}, - {file = "rpds_py-0.18.1.tar.gz", hash = "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, + {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, + {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, + {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, + {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, + {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, + {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, + {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, + {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, + {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, + {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, + {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, + {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, + {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, ] [[package]] @@ -5510,18 +5561,23 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "70.1.1" +version = "74.1.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"}, - {file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"}, + {file = "setuptools-74.1.2-py3-none-any.whl", hash = "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308"}, + {file = "setuptools-74.1.2.tar.gz", hash = "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "shellingham" @@ -5591,13 +5647,13 @@ files = [ [[package]] name = "soupsieve" -version = "2.5" +version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] @@ -5850,60 +5906,60 @@ test = ["pytest"] [[package]] name = "sqlalchemy" -version = "2.0.31" +version = "2.0.34" description = "Database Abstraction Library" optional = true python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, - {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, - {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d0b2cf8791ab5fb9e3aa3d9a79a0d5d51f55b6357eecf532a120ba3b5524db"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:243f92596f4fd4c8bd30ab8e8dd5965afe226363d75cab2468f2c707f64cd83b"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea54f7300553af0a2a7235e9b85f4204e1fc21848f917a3213b0e0818de9a24"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173f5f122d2e1bff8fbd9f7811b7942bead1f5e9f371cdf9e670b327e6703ebd"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:196958cde924a00488e3e83ff917be3b73cd4ed8352bbc0f2989333176d1c54d"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd90c221ed4e60ac9d476db967f436cfcecbd4ef744537c0f2d5291439848768"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-win32.whl", hash = "sha256:3166dfff2d16fe9be3241ee60ece6fcb01cf8e74dd7c5e0b64f8e19fab44911b"}, + {file = "SQLAlchemy-2.0.34-cp310-cp310-win_amd64.whl", hash = "sha256:6831a78bbd3c40f909b3e5233f87341f12d0b34a58f14115c9e94b4cdaf726d3"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7db3db284a0edaebe87f8f6642c2b2c27ed85c3e70064b84d1c9e4ec06d5d84"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:430093fce0efc7941d911d34f75a70084f12f6ca5c15d19595c18753edb7c33b"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79cb400c360c7c210097b147c16a9e4c14688a6402445ac848f296ade6283bbc"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1b30f31a36c7f3fee848391ff77eebdd3af5750bf95fbf9b8b5323edfdb4ec"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fddde2368e777ea2a4891a3fb4341e910a056be0bb15303bf1b92f073b80c02"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80bd73ea335203b125cf1d8e50fef06be709619eb6ab9e7b891ea34b5baa2287"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-win32.whl", hash = "sha256:6daeb8382d0df526372abd9cb795c992e18eed25ef2c43afe518c73f8cccb721"}, + {file = "SQLAlchemy-2.0.34-cp311-cp311-win_amd64.whl", hash = "sha256:5bc08e75ed11693ecb648b7a0a4ed80da6d10845e44be0c98c03f2f880b68ff4"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:53e68b091492c8ed2bd0141e00ad3089bcc6bf0e6ec4142ad6505b4afe64163e"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bcd18441a49499bf5528deaa9dee1f5c01ca491fc2791b13604e8f972877f812"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:165bbe0b376541092bf49542bd9827b048357f4623486096fc9aaa6d4e7c59a2"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97b850f73f8abbffb66ccbab6e55a195a0eb655e5dc74624d15cff4bfb35bd74"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-win32.whl", hash = "sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580"}, + {file = "SQLAlchemy-2.0.34-cp312-cp312-win_amd64.whl", hash = "sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:24af3dc43568f3780b7e1e57c49b41d98b2d940c1fd2e62d65d3928b6f95f021"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60ed6ef0a35c6b76b7640fe452d0e47acc832ccbb8475de549a5cc5f90c2c06"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413c85cd0177c23e32dee6898c67a5f49296640041d98fddb2c40888fe4daa2e"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:25691f4adfb9d5e796fd48bf1432272f95f4bbe5f89c475a788f31232ea6afba"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:526ce723265643dbc4c7efb54f56648cc30e7abe20f387d763364b3ce7506c82"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-win32.whl", hash = "sha256:13be2cc683b76977a700948411a94c67ad8faf542fa7da2a4b167f2244781cf3"}, + {file = "SQLAlchemy-2.0.34-cp37-cp37m-win_amd64.whl", hash = "sha256:e54ef33ea80d464c3dcfe881eb00ad5921b60f8115ea1a30d781653edc2fd6a2"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43f28005141165edd11fbbf1541c920bd29e167b8bbc1fb410d4fe2269c1667a"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b68094b165a9e930aedef90725a8fcfafe9ef95370cbb54abc0464062dbf808f"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1e03db964e9d32f112bae36f0cc1dcd1988d096cfd75d6a588a3c3def9ab2b"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203d46bddeaa7982f9c3cc693e5bc93db476ab5de9d4b4640d5c99ff219bee8c"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ae92bebca3b1e6bd203494e5ef919a60fb6dfe4d9a47ed2453211d3bd451b9f5"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9661268415f450c95f72f0ac1217cc6f10256f860eed85c2ae32e75b60278ad8"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-win32.whl", hash = "sha256:895184dfef8708e15f7516bd930bda7e50ead069280d2ce09ba11781b630a434"}, + {file = "SQLAlchemy-2.0.34-cp38-cp38-win_amd64.whl", hash = "sha256:6e7cde3a2221aa89247944cafb1b26616380e30c63e37ed19ff0bba5e968688d"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dbcdf987f3aceef9763b6d7b1fd3e4ee210ddd26cac421d78b3c206d07b2700b"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce119fc4ce0d64124d37f66a6f2a584fddc3c5001755f8a49f1ca0a177ef9796"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a17d8fac6df9835d8e2b4c5523666e7051d0897a93756518a1fe101c7f47f2f0"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ebc11c54c6ecdd07bb4efbfa1554538982f5432dfb8456958b6d46b9f834bb7"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e6965346fc1491a566e019a4a1d3dfc081ce7ac1a736536367ca305da6472a8"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:220574e78ad986aea8e81ac68821e47ea9202b7e44f251b7ed8c66d9ae3f4278"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-win32.whl", hash = "sha256:b75b00083e7fe6621ce13cfce9d4469c4774e55e8e9d38c305b37f13cf1e874c"}, + {file = "SQLAlchemy-2.0.34-cp39-cp39-win_amd64.whl", hash = "sha256:c29d03e0adf3cc1a8c3ec62d176824972ae29b67a66cbb18daff3062acc6faa8"}, + {file = "SQLAlchemy-2.0.34-py3-none-any.whl", hash = "sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f"}, + {file = "sqlalchemy-2.0.34.tar.gz", hash = "sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22"}, ] [package.dependencies] @@ -5937,13 +5993,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlparse" -version = "0.5.0" +version = "0.5.1" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" files = [ - {file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"}, - {file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"}, + {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, + {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, ] [package.extras] @@ -5971,13 +6027,13 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "starlette" -version = "0.37.2" +version = "0.38.4" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, + {file = "starlette-0.38.4-py3-none-any.whl", hash = "sha256:526f53a77f0e43b85f583438aee1a940fd84f8fd610353e8b0c1a77ad8a87e76"}, + {file = "starlette-0.38.4.tar.gz", hash = "sha256:53a7439060304a208fea17ed407e998f46da5e5d9b1addfea3040094512a6379"}, ] [package.dependencies] @@ -5989,13 +6045,13 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "streamlit" -version = "1.37.1" +version = "1.38.0" description = "A faster way to build and share data apps" optional = false python-versions = "!=3.9.7,>=3.8" files = [ - {file = "streamlit-1.37.1-py2.py3-none-any.whl", hash = "sha256:0651240fccc569900cc9450390b0a67473fda55be65f317e46285f99e2bddf04"}, - {file = "streamlit-1.37.1.tar.gz", hash = "sha256:bc7e3813d94a39dda56f15678437eb37830973c601e8e574f2225a7bf188ea5a"}, + {file = "streamlit-1.38.0-py2.py3-none-any.whl", hash = "sha256:0653ecfe86fef0f1608e3e082aef7eb335d8713f6f31e9c3b19486d1c67d7c41"}, + {file = "streamlit-1.38.0.tar.gz", hash = "sha256:c4bf36b3ef871499ed4594574834583113f93f077dd3035d516d295786f2ad63"}, ] [package.dependencies] @@ -6020,7 +6076,7 @@ typing-extensions = ">=4.3.0,<5" watchdog = {version = ">=2.1.5,<5", markers = "platform_system != \"Darwin\""} [package.extras] -snowflake = ["snowflake-connector-python (>=2.8.0)", "snowflake-snowpark-python (>=0.9.0)"] +snowflake = ["snowflake-connector-python (>=2.8.0)", "snowflake-snowpark-python[modin] (>=1.17.0)"] [[package]] name = "streamlit-chat" @@ -6055,13 +6111,13 @@ cryptg = ["cryptg"] [[package]] name = "tenacity" -version = "8.4.2" +version = "8.5.0" description = "Retry code until it succeeds" optional = false python-versions = ">=3.8" files = [ - {file = "tenacity-8.4.2-py3-none-any.whl", hash = "sha256:9e6f7cf7da729125c7437222f8a522279751cdfbe6b67bfe64f75d3a348661b2"}, - {file = "tenacity-8.4.2.tar.gz", hash = "sha256:cd80a53a79336edba8489e767f729e4f391c896956b57140b5d7511a64bbd3ef"}, + {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, + {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, ] [package.extras] @@ -6145,13 +6201,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.12.5" +version = "0.13.2" description = "Style preserving TOML library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, - {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [[package]] @@ -6176,13 +6232,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.4" +version = "4.66.5" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, - {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, + {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, + {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, ] [package.dependencies] @@ -6211,24 +6267,24 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "trove-classifiers" -version = "2024.5.22" +version = "2024.7.2" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" files = [ - {file = "trove_classifiers-2024.5.22-py3-none-any.whl", hash = "sha256:c43ade18704823e4afa3d9db7083294bc4708a5e02afbcefacd0e9d03a7a24ef"}, - {file = "trove_classifiers-2024.5.22.tar.gz", hash = "sha256:8a6242bbb5c9ae88d34cf665e816b287d2212973c8777dfaef5ec18d72ac1d03"}, + {file = "trove_classifiers-2024.7.2-py3-none-any.whl", hash = "sha256:ccc57a33717644df4daca018e7ec3ef57a835c48e96a1e71fc07eb7edac67af6"}, + {file = "trove_classifiers-2024.7.2.tar.gz", hash = "sha256:8328f2ac2ce3fd773cbb37c765a0ed7a83f89dc564c7d452f039b69249d0ac35"}, ] [[package]] name = "typer" -version = "0.12.3" +version = "0.12.5" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" files = [ - {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, - {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, + {file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b"}, + {file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"}, ] [package.dependencies] @@ -6239,13 +6295,13 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "types-python-dateutil" -version = "2.9.0.20240316" +version = "2.9.0.20240906" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, - {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, + {file = "types-python-dateutil-2.9.0.20240906.tar.gz", hash = "sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e"}, + {file = "types_python_dateutil-2.9.0.20240906-py3-none-any.whl", hash = "sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6"}, ] [[package]] @@ -6304,13 +6360,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "1.26.19" +version = "1.26.20" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, - {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, + {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, + {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, ] [package.extras] @@ -6320,13 +6376,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.30.1" +version = "0.30.6" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" files = [ - {file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"}, - {file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"}, + {file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"}, + {file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"}, ] [package.dependencies] @@ -6339,13 +6395,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.26.3" +version = "20.26.4" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, + {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"}, + {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"}, ] [package.dependencies] @@ -6359,43 +6415,46 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "watchdog" -version = "4.0.1" +version = "4.0.2" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" files = [ - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, - {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, - {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, - {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, - {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, + {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, + {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, + {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, + {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, + {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, + {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, + {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, + {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, + {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, + {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, + {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, + {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, + {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, + {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, ] [package.extras] @@ -6414,13 +6473,13 @@ files = [ [[package]] name = "webcolors" -version = "24.6.0" +version = "24.8.0" description = "A library for working with the color formats defined by HTML and CSS." optional = false python-versions = ">=3.8" files = [ - {file = "webcolors-24.6.0-py3-none-any.whl", hash = "sha256:8cf5bc7e28defd1d48b9e83d5fc30741328305a8195c29a8e668fa45586568a1"}, - {file = "webcolors-24.6.0.tar.gz", hash = "sha256:1d160d1de46b3e81e58d0a280d0c78b467dc80f47294b91b1ad8029d2cedb55b"}, + {file = "webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"}, + {file = "webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d"}, ] [package.extras] @@ -6456,94 +6515,108 @@ test = ["websockets"] [[package]] name = "websockets" -version = "12.0" +version = "13.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"}, + {file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"}, + {file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"}, + {file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"}, + {file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"}, + {file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"}, + {file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"}, + {file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"}, + {file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"}, + {file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"}, + {file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"}, + {file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"}, + {file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"}, + {file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"}, + {file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"}, ] [[package]] name = "werkzeug" -version = "3.0.3" +version = "3.0.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, ] [package.dependencies] @@ -6554,13 +6627,13 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "widgetsnbextension" -version = "4.0.11" +version = "4.0.13" description = "Jupyter interactive widgets for Jupyter Notebook" optional = false python-versions = ">=3.7" files = [ - {file = "widgetsnbextension-4.0.11-py3-none-any.whl", hash = "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36"}, - {file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"}, + {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, + {file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"}, ] [[package]] @@ -6717,101 +6790,103 @@ test = ["pytest"] [[package]] name = "yarl" -version = "1.9.4" +version = "1.10.0" description = "Yet another URL library" optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1718c0bca5a61edac7a57dcc11856cb01bde13a9360a3cb6baf384b89cfc0b40"}, + {file = "yarl-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4657fd290d556a5f3018d07c7b7deadcb622760c0125277d10a11471c340054"}, + {file = "yarl-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:044b76d069e69c6b0246f071ebac0576f89c772f806d66ef51e662bd015d03c7"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5527d32506c11150ca87f33820057dc284e2a01a87f0238555cada247a8b278"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36d12d78b8b0d46099d413c8689b5510ad9ce5e443363d1c37b6ac5b3d7cbdfb"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11f7f8a72b3e26c533fa7ffa7a8068f4e3aad7b67c5cf7b17ea8c79fc81d9830"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88173836a25b7e5dce989eeee3b92d8ef5cdf512830d4155c6212de98e616f70"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c382e189af10070bcb39caa9406b9cc47b26c1d2257979f11fe03a38be09fea9"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:534b8bc181dca1691cf491c263e084af678a8fb6b6181687c788027d8c317026"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5f3372f9ae1d1f001826b77d0b29d4220e84f6c5f53915e71a825cdd02600065"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cca9ba00be4bb8a051c4007b60fc91d6c9728c8b70c86cee4c24be9d641002f"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a9d8c4be5658834dc688072239d220631ad4b71ff79a5f3d17fb653f16d10759"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff45a655ca51e1cb778abbb586083fddb7d896332f47bb3b03bc75e30c25649f"}, + {file = "yarl-1.10.0-cp310-cp310-win32.whl", hash = "sha256:9ef7ce61958b3c7b2e2e0927c52d35cf367c5ee410e06e1337ecc83a90c23b95"}, + {file = "yarl-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:48a48261f8d610b0e15fed033e74798763bc2f8f2c0d769a2a0732511af71f1e"}, + {file = "yarl-1.10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:308d1cce071b5b500e3d95636bbf15dfdb8e87ed081b893555658a7f9869a156"}, + {file = "yarl-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc66927f6362ed613a483c22618f88f014994ccbd0b7a25ec1ebc8c472d4b40a"}, + {file = "yarl-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c4d13071c5b99974cfe2f94c749ecc4baf882f7c4b6e4c40ca3d15d1b7e81f24"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:348ad53acd41caa489df7db352d620c982ab069855d9635dda73d685bbbc3636"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:293f7c2b30d015de3f1441c4ee764963b86636fde881b4d6093498d1e8711f69"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:315e8853d0ea46aabdce01f1f248fff7b9743de89b555c5f0487f54ac84beae8"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:012c506b2c23be4500fb97509aa7e6a575996fb317b80667fa26899d456e2aaf"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f769c2708c31227c5349c3e4c668c8b4b2e25af3e7263723f2ef33e8e3906a0"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f6ac063a4e9bbd4f6cc88cc621516a44d6aec66862ea8399ba063374e4b12c7"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:18b7ce6d8c35da8e16dcc8de124a80e250fc8c73f8c02663acf2485c874f1972"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b80246bdee036381636e73ef0f19b032912064622b0e5ee44f6960fd11df12aa"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:183dd37bb5471e8017ab8a998c1ea070b4a0b08a97a7c4e20e0c7ccbe8ebb999"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9b6d0d7522b514f054b359409817af4c5ed76fa4fe42d8bd1ed12956804cf595"}, + {file = "yarl-1.10.0-cp311-cp311-win32.whl", hash = "sha256:6026a6ef14d038a38ca9d81422db4b6bb7d5da94f9d08f21e0ad9ebd9c4bc3bb"}, + {file = "yarl-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:190e70d2f9f16f1c9d666c103d635c9ed4bf8de7803e9fa0495eec405a3e96a8"}, + {file = "yarl-1.10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6bc602c7413e1b5223bc988947125998cb54d6184de45a871985daacc23e6c8c"}, + {file = "yarl-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bf733c835ebbd52bd78a52b919205e0f06d8571f71976a0259e5bcc20d0a2f44"}, + {file = "yarl-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e91ed5f6818e1e3806eaeb7b14d9e17b90340f23089451ea59a89a29499d760"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23057a004bc9735008eb2a04b6ce94c6c06219cdf2b193997fd3ae6039eb3196"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b922c32a1cff62bc43d408d1a8745abeed0a705793f2253c622bf3521922198"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be199fed28861d72df917e355287ad6835555d8210e7f8203060561f24d7d842"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cece693380c1c4a606cdcaa0c54eda8f72cfe1ba83f5149b9023bb955e8fa8e"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff8e803d8ca170e632fb3b4df1bfd29ba29be8edc3e9306c5ffa5fadea234a4f"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:30dde3a8b88c80a4f049eb4dd240d2a02e89174da6be2525541f949bf9fa38ab"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dff84623e7098cf9bfbb5187f9883051af652b0ce08b9f7084cc8630b87b6457"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e69b55965a47dd6c79e578abd7d85637b1bb4a7565436630826bdb28aa9b7ad"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5d0c9e1dcc92d46ca89608fe4763fc2362f1e81c19a922c67dbc0f20951466e4"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32e79d5ae975f7c2cc29f7104691fc9be5ee3724f24e1a7254d72f6219672108"}, + {file = "yarl-1.10.0-cp312-cp312-win32.whl", hash = "sha256:762a196612c2aba4197cd271da65fe08308f7ddf130dc63842c7a76d774b6a2c"}, + {file = "yarl-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:8c6214071f653d21bb7b43f7ee519afcbf7084263bb43408f4939d14558290db"}, + {file = "yarl-1.10.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0e0aea8319fdc1ac340236e58b0b7dc763621bce6ce98124a9d58104cafd0aaa"}, + {file = "yarl-1.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b3bf343b4ef9ec600d75363eb9b48ab3bd53b53d4e1c5a9fbf0cfe7ba73a47f"}, + {file = "yarl-1.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:05b07e6e0f715eaae9d927a302d9220724392f3c0b4e7f8dfa174bf2e1b8433e"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7bd531d7eec4aa7ef8a99fef91962eeea5158a53af0ec507c476ddf8ebc29c"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:183136dc5d5411872e7529c924189a2e26fac5a7f9769cf13ef854d1d653ad36"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c77a3c10af4aaf8891578fe492ef0990c65cf7005dd371f5ea8007b420958bf6"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:030d41d48217b180c5a176e59c49d212d54d89f6f53640fa4c1a1766492aec27"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4f43ba30d604ba391bc7fe2dd104d6b87b62b0de4bbde79e362524b8a1eb75"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:637dd0f55d1781d4634c23994101c509e455b5ab61af9086b5763b7eca9359aa"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:99e7459ee86a3b81e57777afd3825b8b1acaac8a99f9c0bd02415d80eb3c371b"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a80cdb3c15c15b33ecdb080546dcb022789b0084ca66ad41ffa0fe09857fca11"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1824bfb932d8100e5c94f4f98c078f23ebc6f6fa93acc3d95408762089c54a06"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:90fd64ce00f594db02f603efa502521c440fa1afcf6266be82eb31f19d2d9561"}, + {file = "yarl-1.10.0-cp313-cp313-win32.whl", hash = "sha256:687131ee4d045f3d58128ca28f5047ec902f7760545c39bbe003cc737c5a02b5"}, + {file = "yarl-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:493ad061ee025c5ed3a60893cd70204eead1b3f60ccc90682e752f95b845bd46"}, + {file = "yarl-1.10.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cd65588273d19f8483bc8f32a6fcf602e94a9a7ba287a1725977bd9527cd6c0c"}, + {file = "yarl-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6f64f8681671624f539eea5564518bc924524c25eb90ab24a7eddc2d872e668e"}, + {file = "yarl-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3576ed2c51f8525d4ff5c3279247aacff9540bb43b292c4a37a8e6c6e1691adb"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca42a9281807fdf8fba86e671d8fdd76f92e9302a6d332957f2bae51c774f8a7"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54a4b5e6a060d46cad6a3cf340f4cb268e6fbc89c589d82a2da58f7db47c47c8"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eec21d8c3aa932c5a89480b58fa877e9c48092ab838ccc76788cbc917ceec0d"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:273baee8a8af5989d5aab51c740e65bc2b1fc6619b9dd192cd16a3fae51100be"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1bf63ba496cd4f12d30e916d9a52daa6c91433fedd9cd0d99fef3e13232836f"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f8e24b9a4afdffab399191a9f0b0e80eabc7b7fdb9f2dbccdeb8e4d28e5c57bb"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4c46454fafa31f7241083a0dd21814f63e0fcb4ae49662dc7e286fd6a5160ea1"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:beda87b63c08fb4df8cc5353eeefe68efe12aa4f5284958bd1466b14c85e508e"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:9a8d6a0e2b5617b5c15c59db25f20ba429f1fea810f2c09fbf93067cb21ab085"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b453b3dbc1ed4c2907632d05b378123f3fb411cad05d8d96de7d95104ef11c70"}, + {file = "yarl-1.10.0-cp38-cp38-win32.whl", hash = "sha256:1ea30675fbf0ad6795c100da677ef6a8960a7db05ac5293f02a23c2230203c89"}, + {file = "yarl-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:347011ad09a8f9be3d41fe2d7d611c3a4de4d49aa77bcb9a8c03c7a82fc45248"}, + {file = "yarl-1.10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:18bc4600eed1907762c1816bb16ac63bc52912e53b5e9a353eb0935a78e95496"}, + {file = "yarl-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeb6a40c5ae2616fd38c1e039c6dd50031bbfbc2acacfd7b70a5d64fafc70901"}, + {file = "yarl-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc544248b5263e1c0f61332ccf35e37404b54213f77ed17457f857f40af51452"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3352c69dc235850d6bf8ddad915931f00dcab208ac4248b9af46175204c2f5f9"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af5b52bfbbd5eb208cf1afe23c5ada443929e9b9d79e9fbc66cacc07e4e39748"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eafa7317063de4bc310716cdd9026c13f00b1629e649079a6908c3aafdf5046"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a162cf04fd1e8d81025ec651d14cac4f6e0ca73a3c0a9482de8691b944e3098a"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:179b1df5e9cd99234ea65e63d5bfc6dd524b2c3b6cf68a14b94ccbe01ab37ddd"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:32d2e46848dea122484317485129f080220aa84aeb6a9572ad9015107cebeb07"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aa1aeb99408be0ca774c5126977eb085fedda6dd7d9198ce4ceb2d06a44325c7"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d2366e2f987f69752f0588d2035321aaf24272693d75f7f6bb7e8a0f48f7ccdd"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e8da33665ecc64cd3e593098adb449f9c65b4e3bc6338e75ad592da15453d898"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b46c603bee1f2dd407b8358c2afc9b0472a22ccca528f114e1f4cd30dfecd22"}, + {file = "yarl-1.10.0-cp39-cp39-win32.whl", hash = "sha256:96422a3322b4d954f4c52403a2fc129ad118c151ee60a717847fb46a8480d1e1"}, + {file = "yarl-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:52d1ae09b0764017e330bb5bf9af760c0168c564225085bb806f687bccffda8a"}, + {file = "yarl-1.10.0-py3-none-any.whl", hash = "sha256:99eaa7d53f509ba1c2fea8fdfec15ba3cd36caca31d57ec6665073b148b5f260"}, + {file = "yarl-1.10.0.tar.gz", hash = "sha256:3bf10a395adac62177ba8ea738617e8de6cbb1cea6aa5d5dd2accde704fc8195"}, ] [package.dependencies] @@ -6820,13 +6895,13 @@ multidict = ">=4.0" [[package]] name = "ydb" -version = "3.15.0" +version = "3.16.1" description = "YDB Python SDK" optional = true python-versions = "*" files = [ - {file = "ydb-3.15.0-py2.py3-none-any.whl", hash = "sha256:0eacc4ffa9dbea05464a3118770840efd6d9aca3a445693be22648e3c5124a2a"}, - {file = "ydb-3.15.0.tar.gz", hash = "sha256:84597f9ad78e07d55923a753fc409a17622e1bece8b4ee7192edfde5dc5146fc"}, + {file = "ydb-3.16.1-py2.py3-none-any.whl", hash = "sha256:b6278f6e4dac51519b0db705d667e9a279fab72b987b358e386014dbeff4ee26"}, + {file = "ydb-3.16.1.tar.gz", hash = "sha256:fd18976146ff4d65cff13a8265911bb40ff67312398b0ca80529d74564c4c6fa"}, ] [package.dependencies] @@ -6840,18 +6915,22 @@ yc = ["yandexcloud"] [[package]] name = "zipp" -version = "3.19.2" +version = "3.20.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, + {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, + {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [[package]] name = "zope-event" @@ -6873,47 +6952,45 @@ test = ["zope.testrunner"] [[package]] name = "zope-interface" -version = "6.4.post2" +version = "7.0.3" description = "Interfaces for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "zope.interface-6.4.post2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2eccd5bef45883802848f821d940367c1d0ad588de71e5cabe3813175444202c"}, - {file = "zope.interface-6.4.post2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:762e616199f6319bb98e7f4f27d254c84c5fb1c25c908c2a9d0f92b92fb27530"}, - {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef8356f16b1a83609f7a992a6e33d792bb5eff2370712c9eaae0d02e1924341"}, - {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e4fa5d34d7973e6b0efa46fe4405090f3b406f64b6290facbb19dcbf642ad6b"}, - {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d22fce0b0f5715cdac082e35a9e735a1752dc8585f005d045abb1a7c20e197f9"}, - {file = "zope.interface-6.4.post2-cp310-cp310-win_amd64.whl", hash = "sha256:97e615eab34bd8477c3f34197a17ce08c648d38467489359cb9eb7394f1083f7"}, - {file = "zope.interface-6.4.post2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599f3b07bde2627e163ce484d5497a54a0a8437779362395c6b25e68c6590ede"}, - {file = "zope.interface-6.4.post2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:136cacdde1a2c5e5bc3d0b2a1beed733f97e2dad8c2ad3c2e17116f6590a3827"}, - {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47937cf2e7ed4e0e37f7851c76edeb8543ec9b0eae149b36ecd26176ff1ca874"}, - {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0a6be264afb094975b5ef55c911379d6989caa87c4e558814ec4f5125cfa2e"}, - {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47654177e675bafdf4e4738ce58cdc5c6d6ee2157ac0a78a3fa460942b9d64a8"}, - {file = "zope.interface-6.4.post2-cp311-cp311-win_amd64.whl", hash = "sha256:e2fb8e8158306567a3a9a41670c1ff99d0567d7fc96fa93b7abf8b519a46b250"}, - {file = "zope.interface-6.4.post2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b912750b13d76af8aac45ddf4679535def304b2a48a07989ec736508d0bbfbde"}, - {file = "zope.interface-6.4.post2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ac46298e0143d91e4644a27a769d1388d5d89e82ee0cf37bf2b0b001b9712a4"}, - {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86a94af4a88110ed4bb8961f5ac72edf782958e665d5bfceaab6bf388420a78b"}, - {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73f9752cf3596771c7726f7eea5b9e634ad47c6d863043589a1c3bb31325c7eb"}, - {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b5c3e9744dcdc9e84c24ed6646d5cf0cf66551347b310b3ffd70f056535854"}, - {file = "zope.interface-6.4.post2-cp312-cp312-win_amd64.whl", hash = "sha256:551db2fe892fcbefb38f6f81ffa62de11090c8119fd4e66a60f3adff70751ec7"}, - {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96ac6b3169940a8cd57b4f2b8edcad8f5213b60efcd197d59fbe52f0accd66e"}, - {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cebff2fe5dc82cb22122e4e1225e00a4a506b1a16fafa911142ee124febf2c9e"}, - {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33ee982237cffaf946db365c3a6ebaa37855d8e3ca5800f6f48890209c1cfefc"}, - {file = "zope.interface-6.4.post2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:fbf649bc77510ef2521cf797700b96167bb77838c40780da7ea3edd8b78044d1"}, - {file = "zope.interface-6.4.post2-cp37-cp37m-win_amd64.whl", hash = "sha256:4c0b208a5d6c81434bdfa0f06d9b667e5de15af84d8cae5723c3a33ba6611b82"}, - {file = "zope.interface-6.4.post2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3fe667935e9562407c2511570dca14604a654988a13d8725667e95161d92e9b"}, - {file = "zope.interface-6.4.post2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a96e6d4074db29b152222c34d7eec2e2db2f92638d2b2b2c704f9e8db3ae0edc"}, - {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:866a0f583be79f0def667a5d2c60b7b4cc68f0c0a470f227e1122691b443c934"}, - {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fe919027f29b12f7a2562ba0daf3e045cb388f844e022552a5674fcdf5d21f1"}, - {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e0343a6e06d94f6b6ac52fbc75269b41dd3c57066541a6c76517f69fe67cb43"}, - {file = "zope.interface-6.4.post2-cp38-cp38-win_amd64.whl", hash = "sha256:dabb70a6e3d9c22df50e08dc55b14ca2a99da95a2d941954255ac76fd6982bc5"}, - {file = "zope.interface-6.4.post2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:706efc19f9679a1b425d6fa2b4bc770d976d0984335eaea0869bd32f627591d2"}, - {file = "zope.interface-6.4.post2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d136e5b8821073e1a09dde3eb076ea9988e7010c54ffe4d39701adf0c303438"}, - {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1730c93a38b5a18d24549bc81613223962a19d457cfda9bdc66e542f475a36f4"}, - {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc2676312cc3468a25aac001ec727168994ea3b69b48914944a44c6a0b251e79"}, - {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a62fd6cd518693568e23e02f41816adedfca637f26716837681c90b36af3671"}, - {file = "zope.interface-6.4.post2-cp39-cp39-win_amd64.whl", hash = "sha256:d3f7e001328bd6466b3414215f66dde3c7c13d8025a9c160a75d7b2687090d15"}, - {file = "zope.interface-6.4.post2.tar.gz", hash = "sha256:1c207e6f6dfd5749a26f5a5fd966602d6b824ec00d2df84a7e9a924e8933654e"}, + {file = "zope.interface-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b9369671a20b8d039b8e5a1a33abd12e089e319a3383b4cc0bf5c67bd05fe7b"}, + {file = "zope.interface-7.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca"}, + {file = "zope.interface-7.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53d678bb1c3b784edbfb0adeebfeea6bf479f54da082854406a8f295d36f8386"}, + {file = "zope.interface-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3aa8fcbb0d3c2be1bfd013a0f0acd636f6ed570c287743ae2bbd467ee967154d"}, + {file = "zope.interface-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58"}, + {file = "zope.interface-7.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:11fa1382c3efb34abf16becff8cb214b0b2e3144057c90611621f2d186b7e1b7"}, + {file = "zope.interface-7.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af94e429f9d57b36e71ef4e6865182090648aada0cb2d397ae2b3f7fc478493a"}, + {file = "zope.interface-7.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dd647fcd765030638577fe6984284e0ebba1a1008244c8a38824be096e37fe3"}, + {file = "zope.interface-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bee1b722077d08721005e8da493ef3adf0b7908e0cd85cc7dc836ac117d6f32"}, + {file = "zope.interface-7.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2545d6d7aac425d528cd9bf0d9e55fcd47ab7fd15f41a64b1c4bf4c6b24946dc"}, + {file = "zope.interface-7.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d04b11ea47c9c369d66340dbe51e9031df2a0de97d68f442305ed7625ad6493"}, + {file = "zope.interface-7.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:064ade95cb54c840647205987c7b557f75d2b2f7d1a84bfab4cf81822ef6e7d1"}, + {file = "zope.interface-7.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd"}, + {file = "zope.interface-7.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3d4b91821305c8d8f6e6207639abcbdaf186db682e521af7855d0bea3047c8ca"}, + {file = "zope.interface-7.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35062d93bc49bd9b191331c897a96155ffdad10744ab812485b6bad5b588d7e4"}, + {file = "zope.interface-7.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd"}, + {file = "zope.interface-7.0.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05"}, + {file = "zope.interface-7.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:3de1d553ce72868b77a7e9d598c9bff6d3816ad2b4cc81c04f9d8914603814f3"}, + {file = "zope.interface-7.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab985c566a99cc5f73bc2741d93f1ed24a2cc9da3890144d37b9582965aff996"}, + {file = "zope.interface-7.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b"}, + {file = "zope.interface-7.0.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b"}, + {file = "zope.interface-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:382d31d1e68877061daaa6499468e9eb38eb7625d4369b1615ac08d3860fe896"}, + {file = "zope.interface-7.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c4316a30e216f51acbd9fb318aa5af2e362b716596d82cbb92f9101c8f8d2e7"}, + {file = "zope.interface-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d"}, + {file = "zope.interface-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799ef7a444aebbad5a145c3b34bff012b54453cddbde3332d47ca07225792ea4"}, + {file = "zope.interface-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3b7ce6d46fb0e60897d62d1ff370790ce50a57d40a651db91a3dde74f73b738"}, + {file = "zope.interface-7.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:f418c88f09c3ba159b95a9d1cfcdbe58f208443abb1f3109f4b9b12fd60b187c"}, + {file = "zope.interface-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:84f8794bd59ca7d09d8fce43ae1b571be22f52748169d01a13d3ece8394d8b5b"}, + {file = "zope.interface-7.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7d92920416f31786bc1b2f34cc4fc4263a35a407425319572cbf96b51e835cd3"}, + {file = "zope.interface-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e5913ec718010dc0e7c215d79a9683b4990e7026828eedfda5268e74e73e11"}, + {file = "zope.interface-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1eeeb92cb7d95c45e726e3c1afe7707919370addae7ed14f614e22217a536958"}, + {file = "zope.interface-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd32f30f40bfd8511b17666895831a51b532e93fc106bfa97f366589d3e4e0e"}, + {file = "zope.interface-7.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:5112c530fa8aa2108a3196b9c2f078f5738c1c37cfc716970edc0df0414acda8"}, + {file = "zope.interface-7.0.3.tar.gz", hash = "sha256:cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1"}, ] [package.dependencies]