diff --git a/.github/workflows/code-linting.yml b/.github/workflows/code-linting.yml new file mode 100644 index 0000000..a98e531 --- /dev/null +++ b/.github/workflows/code-linting.yml @@ -0,0 +1,49 @@ +name: Code linting + +on: + pull_request: + branches: [master] + +jobs: + black: + runs-on: ubuntu-latest + steps: + - name: Check Out Repository + uses: actions/checkout@v4 + + - name: Run black + uses: psf/black@stable + with: + options: "--check --diff --config pyproject.toml" + + isort: + runs-on: ubuntu-latest + steps: + - name: Check Out Repository + uses: actions/checkout@v4 + + - name: Run isort + uses: isort/isort-action@master + + mypy: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Check Out Repository + uses: actions/checkout@v4 + + - name: Set up python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: poetry install --no-interaction --no-root --all-extras + + - name: Check + run: poetry run mypy . --config-file pyproject.toml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..88d5850 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,30 @@ +name: Release +on: + release: + types: + - published + +jobs: + publish: + strategy: + fail-fast: false + matrix: + python-version: [3.12] + poetry-version: [1.8.2] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Publish + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: | + poetry config pypi-token.pypi $PYPI_TOKEN + poetry publish --build diff --git a/README.md b/README.md index 4787290..0437b6c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## About SuvvyAPI πŸ“˜ -SuvvyAPI is an asynchronous Python API wrapper built on top of `httpx` and `pydantic` for the Suvvy AI API, offering an easy and Pythonic way to interact with the Suvvy AI services. +SuvvyAPI is an asynchronous Python API wrapper built on top of `httpx` for the Suvvy AI API, offering an easy and Pythonic way to interact with the Suvvy AI services. ## Installation πŸ› οΈ @@ -25,18 +25,26 @@ pip install -U suvvyapi ## Usage πŸš€ -### Synchronous Usage +### Asynchronous Usage -You can use SuvvyAPI synchronously as follows: +You can use SuvvyAPI asynchronously as follows: ```python -from suvvyapi import Suvvy, Message +import asyncio +from suvvyapi import AsyncSuvvy + + +async def main(): + async with AsyncSuvvy( + token="", + ) as suvvy: + await suvvy.send_message(chat_id="somechat1", text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!", source="Иван") + + +asyncio.run(main()) -suvvy = Suvvy("YOUR_TOKEN") -history = suvvy.as_history("random_id") -response = history.predict_add_message(Message(text="Say hello to Python!")) ``` -*Note: Replace "YOUR_TOKEN" with your actual token from [Suvvy AI](https://home.suvvy.ai/).* +*Note: Replace "your token" with your actual token from [Suvvy AI](https://app.suvvy.ai/).* ### [More in documentation](https://github.com/suvvyai/suvvyapi/wiki) @@ -50,4 +58,4 @@ Contributions are welcome. Please fork the repository, make your changes, and su ## License πŸ“„ -SuvvyAPI is released under the [MIT License](https://github.com/suvvyai/suvvyapi/blob/main/LICENSE). \ No newline at end of file +SuvvyAPI is released under the [MIT License](https://github.com/suvvyai/suvvyapi/blob/main/LICENSE). diff --git a/poetry.lock b/poetry.lock index f300510..83371e7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,55 +1,26 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, -] +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "anyio" -version = "4.2.0" +version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, + {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "asttokens" -version = "2.4.1" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = "*" -files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, -] - -[package.dependencies] -six = ">=1.12.0" - -[package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "black" @@ -99,24 +70,24 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -133,64 +104,20 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "deprecation" -version = "2.1.0" -description = "A library to handle automated deprecations" -optional = false -python-versions = "*" -files = [ - {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, - {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, -] - -[package.dependencies] -packaging = "*" - -[[package]] -name = "devtools" -version = "0.12.2" -description = "Python's missing debug print command, and more." -optional = false -python-versions = ">=3.7" -files = [ - {file = "devtools-0.12.2-py3-none-any.whl", hash = "sha256:c366e3de1df4cdd635f1ad8cbcd3af01a384d7abda71900e68d43b04eb6aaca7"}, - {file = "devtools-0.12.2.tar.gz", hash = "sha256:efceab184cb35e3a11fa8e602cc4fadacaa2e859e920fc6f87bf130b69885507"}, -] - -[package.dependencies] -asttokens = ">=2.0.0,<3.0.0" -executing = ">=1.1.1" -pygments = ">=2.15.0" - [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "executing" -version = "2.0.1" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = ">=3.5" -files = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, -] - -[package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - [[package]] name = "h11" version = "0.14.0" @@ -204,13 +131,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, ] [package.dependencies] @@ -221,7 +148,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" @@ -249,25 +176,17 @@ socks = ["socksio (==1.*)"] [[package]] name = "idna" -version = "3.6" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "isort" @@ -285,47 +204,53 @@ colors = ["colorama (>=0.4.6)"] [[package]] name = "mypy" -version = "1.8.0" +version = "1.14.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87"}, + {file = "mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179"}, + {file = "mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e"}, + {file = "mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3"}, + {file = "mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44"}, + {file = "mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a"}, + {file = "mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc"}, + {file = "mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015"}, + {file = "mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb"}, + {file = "mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc"}, + {file = "mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd"}, + {file = "mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1"}, + {file = "mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63"}, + {file = "mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d"}, + {file = "mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba"}, + {file = "mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741"}, + {file = "mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7"}, + {file = "mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8"}, + {file = "mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc"}, + {file = "mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f"}, + {file = "mypy-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b16738b1d80ec4334654e89e798eb705ac0c36c8a5c4798496cd3623aa02286"}, + {file = "mypy-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10065fcebb7c66df04b05fc799a854b1ae24d9963c8bb27e9064a9bdb43aa8ad"}, + {file = "mypy-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbb7d683fa6bdecaa106e8368aa973ecc0ddb79a9eaeb4b821591ecd07e9e03c"}, + {file = "mypy-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3498cb55448dc5533e438cd13d6ddd28654559c8c4d1fd4b5ca57a31b81bac01"}, + {file = "mypy-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c7b243408ea43755f3a21a0a08e5c5ae30eddb4c58a80f415ca6b118816e60aa"}, + {file = "mypy-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14117b9da3305b39860d0aa34b8f1ff74d209a368829a584eb77524389a9c13e"}, + {file = "mypy-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af98c5a958f9c37404bd4eef2f920b94874507e146ed6ee559f185b8809c44cc"}, + {file = "mypy-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b343a1d3989547024377c2ba0dca9c74a2428ad6ed24283c213af8dbb0710b"}, + {file = "mypy-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cdb5563c1726c85fb201be383168f8c866032db95e1095600806625b3a648cb7"}, + {file = "mypy-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:74e925649c1ee0a79aa7448baf2668d81cc287dc5782cff6a04ee93f40fb8d3f"}, + {file = "mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab"}, + {file = "mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6"}, ] [package.dependencies] -mypy-extensions = ">=1.0.0" +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)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -343,13 +268,13 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.2" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -365,270 +290,95 @@ files = [ [[package]] name = "platformdirs" -version = "4.1.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.3.6" +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.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] -name = "pluggy" -version = "1.3.0" -description = "plugin and hook calling mechanisms for python" +name = "python-magic" +version = "0.4.27" +description = "File type identification using libmagic" optional = false -python-versions = ">=3.8" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pydantic" -version = "2.5.3" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, - {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.14.6" -typing-extensions = ">=4.6.1" - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.14.6" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, - {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, - {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, - {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, - {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, - {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, - {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, - {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, - {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, - {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, - {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, - {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, - {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, - {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, - {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, - {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "7.4.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.23.2" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-asyncio-0.23.2.tar.gz", hash = "sha256:c16052382554c7b22d48782ab3438d5b10f8cf7a4bdcae7f0f67f097d95beecc"}, - {file = "pytest_asyncio-0.23.2-py3-none-any.whl", hash = "sha256:ea9021364e32d58f0be43b91c6233fb8d2224ccef2398d6837559e587682808f"}, -] - -[package.dependencies] -pytest = ">=7.0.0" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, + {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, ] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [metadata] lock-version = "2.0" -python-versions = ">=3.10" -content-hash = "ea5f86949707db386188c825bccde87a71925f2d87e5c888abcfa69fb13410e2" +python-versions = ">=3.10,<4.0" +content-hash = "e3536e935e5d4659e627b23ccac13d735f90c946079a7bf5bd6edff3118447a8" diff --git a/pyproject.toml b/pyproject.toml index f045559..d6cebb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "suvvyapi" -version = "1.0.2" +version = "3.0" description = "A Python API wrapper for Suvvy AI API" authors = ["Roman Poltorabatko "] license = "MIT" @@ -23,17 +23,13 @@ repository = "https://github.com/suvvyai/suvvyapi" "Documentation" = "https://github.com/suvvyai/suvvyapi/wiki" [tool.poetry.dependencies] -python = ">=3.10" +python = ">=3.10,<4.0" httpx = "^0.26.0" -pydantic = "^2.5.3" -deprecation = "^2.1.0" +python-magic = "^0.4.27" [tool.poetry.group.dev.dependencies] -devtools = "^0.12.2" black = "^23.12.0" isort = "^5.13.2" -pytest = "^7.4.3" -pytest-asyncio = "^0.23.2" mypy = "^1.8.0" [tool.isort] diff --git a/suvvyapi/__init__.py b/suvvyapi/__init__.py index 567ed29..060fa6d 100644 --- a/suvvyapi/__init__.py +++ b/suvvyapi/__init__.py @@ -1,29 +1,3 @@ -from suvvyapi.asynchronous.wrapper import AsyncSuvvyAPIWrapper -from suvvyapi.models.history import ( - ChatHistory, - Message, - HistoryMessage, - FunctionDetails, -) -from suvvyapi.models.responses import ( - Prediction, - LLMResult, - TokenUsage, - BalanceUsage, -) -from suvvyapi.sync.wrapper import SuvvyAPIWrapper -from suvvyapi.wrapper import Suvvy +from suvvyapi.api.async_ import AsyncSuvvy -__all__ = [ - "AsyncSuvvyAPIWrapper", - "SuvvyAPIWrapper", - "Suvvy", - "Prediction", - "LLMResult", - "TokenUsage", - "BalanceUsage", - "ChatHistory", - "Message", - "HistoryMessage", - "FunctionDetails", -] +__all__ = ["AsyncSuvvy"] diff --git a/suvvyapi/_utils.py b/suvvyapi/_utils.py deleted file mode 100644 index 67784e6..0000000 --- a/suvvyapi/_utils.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Any - - -def _merge_dicts(*dicts: dict[Any, Any]) -> dict[Any, Any]: - merged_dict: dict[Any, Any] = {} - for d in dicts: - merged_dict |= d - return merged_dict diff --git a/suvvyapi/asynchronous/__init__.py b/suvvyapi/api/__init__.py similarity index 100% rename from suvvyapi/asynchronous/__init__.py rename to suvvyapi/api/__init__.py diff --git a/suvvyapi/api/async_.py b/suvvyapi/api/async_.py new file mode 100644 index 0000000..258cfdc --- /dev/null +++ b/suvvyapi/api/async_.py @@ -0,0 +1,95 @@ +import string +from random import choices +from typing import BinaryIO + +import httpx + +from suvvyapi.types.attachment import Attachment, input_to_attachments +from suvvyapi.types.enum import SuvvyMessageSender +from suvvyapi.types.exceptions import ( + InvalidToken, CustomChannelDisabled, InvalidFileType, NegativeBalance, + ValidationError, SuvvyError +) +from suvvyapi.types.link import Link + + +class AsyncSuvvy(httpx.AsyncClient): + """Asynchronous client for Suvvy AI API""" + + def __init__( + self, + token: str, + *, + base_url: str = "https://api.suvvy.ai", + ) -> None: + super().__init__( + base_url=base_url, + headers={"Authorization": f"Bearer {token}"}, + timeout=30, + ) + + async def send_message( + self, + chat_id: str, + source: str, + text: str | None = None, + attachments: list[Attachment | str | tuple[str, bytes] | BinaryIO] + | None = None, + placeholders: dict[str, str] | None = None, + link: Link | None = None, + client_name: str | None = None, + client_phone: str | None = None, + message_sender: SuvvyMessageSender = SuvvyMessageSender.CUSTOMER, + message_id: str | None = None, + ) -> None: + """Send message to Suvvy + + :param chat_id: Chat ID + :param source: Source + :param text: Message text. Required if attachments are not specified. + :param attachments: Attachments. Required if text is not specified. + :param placeholders: Instruction placeholders + :param link: Link + :param client_name: Client name + :param client_phone: Client phone + :param message_sender: Message sender (customer or employee) + :param message_id: Message ID. Used to prevent repeated answers. Created randomly if not specified. + """ + message_id = message_id or "".join( + choices(string.ascii_letters + string.digits, k=10) + ) + if text is None and not attachments: + raise ValueError("Either text or attachments must be specified") + + response = await self.post( + "/api/webhook/custom/message", + json={ + "api_version": 1, + "chat_id": chat_id, + "source": source, + "text": text, + "attachments": [a.to_dict() for a in input_to_attachments(attachments)] if attachments else [], + "placeholders": placeholders or {}, + "link": link.to_dict() if link is not None else None, + "client_name": client_name, + "client_phone": client_phone, + "message_sender": message_sender.value, + "message_id": message_id, + }, + ) + + match response.status_code: + case 200: + return + case 401: + raise InvalidToken + case 424: + raise CustomChannelDisabled + case 415: + raise InvalidFileType(f"Allowed types: {response.json()["allowed_mime_types"]}") + case 402: + raise NegativeBalance + case 422: + raise ValidationError(response.json()) + case _: + raise SuvvyError(response.json()) diff --git a/suvvyapi/asynchronous/wrapper.py b/suvvyapi/asynchronous/wrapper.py deleted file mode 100644 index f1cb405..0000000 --- a/suvvyapi/asynchronous/wrapper.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import Literal - -import deprecation -import httpx - -from suvvyapi.exceptions.api import ( - HistoryNotFoundError, - HistoryStoppedError, - HistoryTooLongError, - InternalAPIError, - InvalidAPITokenError, - MessageLimitExceededError, - NegativeBalanceError, - UnknownAPIError, -) -from suvvyapi.models.history import ChatHistory, Message -from suvvyapi.models.responses import Prediction - - -class AsyncSuvvyAPIWrapper: - @deprecation.deprecated( - deprecated_in="1.0.0", - removed_in="2.0.0", - details="Use the Suvvy() class instead", - ) - def __init__( - self, - token: str, - base_url: str = "https://api.suvvy.ai/", - placeholders: dict | None = None, - custom_log_info: dict | None = None, - ) -> None: - self.token = token - self.base_url = base_url.lstrip("/") - self.placeholders = placeholders or {} - self.custom_log_info = custom_log_info or {} - - async def _make_request( - self, - method: Literal["GET", "POST", "PUT", "DELETE"], - path: str, - body: dict | None = None, - ) -> httpx.Response: - headers = {"Authorization": f"bearer {self.token}"} - async with httpx.AsyncClient( - headers=headers, base_url=self.base_url, timeout=300 - ) as c: - response = await c.request(method=method, url=path, json=body) - if response.status_code == 401: - raise InvalidAPITokenError("API Token is invalid.") - if response.status_code == 402: - raise NegativeBalanceError.from_detail(response.json()["detail"]) - if response.status_code == 500: - raise InternalAPIError( - "Internal API error occurred. Contact suvvy.ai support." - ) - return response - - async def get_history(self, unique_id: str) -> ChatHistory: - response = await self._make_request( - method="GET", path=f"/api/v1/history?unique_id={unique_id}" - ) - json = response.json() - history = ChatHistory(**json) - return history - - async def reset_history(self, unique_id: str) -> None: - await self._make_request( - method="PUT", path=f"/api/v1/history?unique_id={unique_id}" - ) - - async def add_message( - self, - message: Message | list[Message], - unique_id: str, - pass_ai_as_employee: bool = True, - ) -> None: - if not isinstance(message, list): - message = [message] - - _ms = [] - for m in message: - _ms.append(m.model_dump()) - - message = _ms - - body = {"messages": message, "pass_ai_as_employee": pass_ai_as_employee} - await self._make_request( - method="POST", - path=f"/api/v1/history/message?unique_id={unique_id}", - body=body, - ) - - async def predict_from_history( - self, - unique_id: str, - placeholders: dict | None = None, - auto_insert_ai: bool = True, - custom_log_info: dict | None = None, - raise_if_dialog_stopped: bool = False, - ) -> Prediction: - placeholders = placeholders or {} - custom_log_info = custom_log_info or {} - - custom_log_info = dict(**self.custom_log_info, **custom_log_info) - placeholders = dict(**self.placeholders, **placeholders) - - body = { - "placeholders": placeholders, - "custom_log_info": custom_log_info, - "auto_insert_ai": auto_insert_ai, - } - response = await self._make_request( - method="POST", - path=f"/api/v1/history/predict?unique_id={unique_id}", - body=body, - ) - match response.status_code: - case 202: - if raise_if_dialog_stopped: - raise HistoryStoppedError("History is marked as stopped") - else: - prediction = Prediction() - return prediction - case 404: - raise HistoryNotFoundError() - case 413: - json = response.json() - detail = json["detail"] - if detail.startswith("Maximum token limit"): - raise HistoryTooLongError("History is too long to process") - else: - raise MessageLimitExceededError( - "Message limit for that instance is exceeded" - ) - case 200: - pass - case _: - raise UnknownAPIError( - f"We don't know what happened. Status code is {response.status_code}" - ) - - json = response.json() - prediction = Prediction(**json) - return prediction - - async def predict( - self, - message: Message | list[Message], - unique_id: str, - pass_ai_as_employee: bool = True, - placeholders: dict | None = None, - auto_insert_ai: bool = True, - custom_log_info: dict | None = None, - raise_if_dialog_stopped: bool = False, - ) -> Prediction: - placeholders = placeholders or {} - custom_log_info = custom_log_info or {} - - custom_log_info = dict(**self.custom_log_info, **custom_log_info) - placeholders = dict(**self.placeholders, **placeholders) - - if not isinstance(message, list): - message = [message] - - _ms = [] - for m in message: - _ms.append(m.model_dump()) - - message = _ms - - body = { - "placeholders": placeholders, - "custom_log_info": custom_log_info, - "auto_insert_ai": auto_insert_ai, - "messages": message, - "pass_ai_as_employee": pass_ai_as_employee, - } - response = await self._make_request( - method="POST", - path=f"/api/v1/history/message/predict?unique_id={unique_id}", - body=body, - ) - match response.status_code: - case 202: - if raise_if_dialog_stopped: - raise HistoryStoppedError("History is marked as stopped") - else: - prediction = Prediction() - return prediction - case 404: - raise HistoryNotFoundError() - case 413: - json = response.json() - detail = json["detail"] - if detail.startswith("Maximum token limit"): - raise HistoryTooLongError("History is too long to process") - else: - raise MessageLimitExceededError( - "Message limit for that instance is exceeded" - ) - case 200: - pass - case _: - raise UnknownAPIError( - f"We don't know what happened. Status code is {response.status_code}" - ) - - json = response.json() - prediction = Prediction(**json) - return prediction diff --git a/suvvyapi/enums/__init__.py b/suvvyapi/enums/__init__.py deleted file mode 100644 index fc803bb..0000000 --- a/suvvyapi/enums/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from enum import Enum - - -class Role(str, Enum): - HUMAN = "human" - AI = "ai" - FUNCTION_CALL = "function_call" - FUNCTION_RESULT = "function_result" - IMAGE_MESSAGE = "sent_image" - AUDIO_MESSAGE = "audio_message" diff --git a/suvvyapi/exceptions/api.py b/suvvyapi/exceptions/api.py deleted file mode 100644 index 2708425..0000000 --- a/suvvyapi/exceptions/api.py +++ /dev/null @@ -1,56 +0,0 @@ -import re -from typing import Optional - - -class InvalidAPITokenError(BaseException): - """Raised, when API token is invalid""" - - -class NegativeBalanceError(BaseException): - """Raised, when your Suvvy AI balance is under zero""" - - balance: Optional[int] = None - - @classmethod - def from_detail(cls, detail: str) -> "NegativeBalanceError": - exc = cls("Your balance is under zero") - - balance_match = re.search(pattern=r"(\(\d*\))", string=detail) - if balance_match is None: - return exc - - balance = int(balance_match.group(0).strip("()")) - exc.balance = balance - return exc - - -class HistoryStoppedError(BaseException): - """Raised, when history is marked as stopped""" - - -class HistoryNotFoundError(BaseException): - """Raised, when history with this unique id is not found""" - - -class MessageNotFoundError(BaseException): - """Raised, when message with this message_id is not found""" - - -class InternalMessageAdded(BaseException): - """Raised, when message with internal role or role-specific information is added""" - - -class HistoryTooLongError(BaseException): - """Raised, when history is too long to process""" - - -class MessageLimitExceededError(BaseException): - """Raised, when message limit for that instance is exceeded""" - - -class UnknownAPIError(BaseException): - """Raised, when WE DON'T KNOW WHAT HAPPENED""" - - -class InternalAPIError(BaseException): - """Raised, when internal api error occurred""" diff --git a/suvvyapi/history/__init__.py b/suvvyapi/history/__init__.py deleted file mode 100644 index 1d37673..0000000 --- a/suvvyapi/history/__init__.py +++ /dev/null @@ -1,77 +0,0 @@ -from suvvyapi import Suvvy, Prediction, Message, ChatHistory - - -class History(object): - def __init__(self, unique_id: str, suvvy: Suvvy): - self._suvvy = suvvy - self.unique_id = unique_id - - def predict( - self, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Get answer from AI""" - return self._suvvy.predict_history( - self.unique_id, placeholders, custom_log_info, source - ) - - async def apredict( - self, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Get answer from AI""" - return await self._suvvy.apredict_history( - self.unique_id, placeholders, custom_log_info, source - ) - - def predict_add_message( - self, - message: list[Message] | Message, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Add message and get answer from AI""" - return self._suvvy.predict_history_add_message( - self.unique_id, message, placeholders, custom_log_info, source - ) - - async def apredict_add_message( - self, - message: list[Message] | Message, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Add message and get answer from AI""" - return await self._suvvy.apredict_history_add_message( - self.unique_id, message, placeholders, custom_log_info, source - ) - - def get(self) -> ChatHistory: - """Get history""" - return self._suvvy.get_history(self.unique_id) - - async def aget(self) -> ChatHistory: - """Get history""" - return await self._suvvy.aget_history(self.unique_id) - - def reset(self) -> ChatHistory: - """Reset history""" - return self._suvvy.reset_history(self.unique_id) - - async def areset(self) -> ChatHistory: - """Reset history""" - return await self._suvvy.areset_history(self.unique_id) - - def add_message(self, message: list[Message] | Message) -> ChatHistory: - """Get history""" - return self._suvvy.add_message_to_history(self.unique_id, message) - - async def async_add_message(self, message: list[Message] | Message) -> ChatHistory: - """Get history""" - return await self._suvvy.async_add_message_to_history(self.unique_id, message) diff --git a/suvvyapi/models/__init__.py b/suvvyapi/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/suvvyapi/models/history.py b/suvvyapi/models/history.py deleted file mode 100644 index ea993b0..0000000 --- a/suvvyapi/models/history.py +++ /dev/null @@ -1,41 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - -from suvvyapi.enums import Role - - -class FunctionDetails(BaseModel): - name: str - args: Optional[dict] = None - - -class Message(BaseModel): - model_config = ConfigDict(use_enum_values=True) - - text: str = Field(default="", description="Message text, function result") - role: Role = Role.HUMAN - function: Optional[FunctionDetails] = Field( - default=None, - description='Needed for functions. Unused if role != "function_*".', - ) - - -class HistoryMessage(Message): - tokens: int = 0 - time: datetime - message_id: int - context: str = "" - - -class ChatHistory(BaseModel): - history: list[HistoryMessage] - unique_id: str - stopped: bool = False - stop_reason: str = "unknown" - last_interaction_time: datetime - created_time: datetime - channel_name: str - last_source: str | None = None - last_instance_id: int | None = None diff --git a/suvvyapi/models/responses.py b/suvvyapi/models/responses.py deleted file mode 100644 index 03b6c56..0000000 --- a/suvvyapi/models/responses.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel - -from suvvyapi.models.history import HistoryMessage - - -class TokenUsage(BaseModel): - prompt_tokens: int = 0 - completion_tokens: int = 0 - total_tokens: int = 0 - - -class BalanceUsage(BaseModel): - prompt_tokens: int = 0 - completion_tokens: int = 0 - knowledge_usage: int = 0 - function_usage: int = 0 - total_tokens: int = 0 - token_multiplier: float = 1.0 - - -class LLMResult(BaseModel): - token_usage: TokenUsage = TokenUsage() - balance_usage: BalanceUsage = BalanceUsage() - - -class Prediction(BaseModel): - generation_info: LLMResult = LLMResult() - new_messages: list[HistoryMessage] = [] - - @property - def actual_response(self) -> HistoryMessage | None: - if self.new_messages: - return self.new_messages[-1] - else: - return None diff --git a/suvvyapi/sync/__init__.py b/suvvyapi/sync/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/suvvyapi/sync/wrapper.py b/suvvyapi/sync/wrapper.py deleted file mode 100644 index 22cdba2..0000000 --- a/suvvyapi/sync/wrapper.py +++ /dev/null @@ -1,212 +0,0 @@ -from typing import Literal - -import deprecation -import httpx - -from suvvyapi.exceptions.api import ( - HistoryNotFoundError, - HistoryStoppedError, - HistoryTooLongError, - InternalAPIError, - InvalidAPITokenError, - MessageLimitExceededError, - NegativeBalanceError, - UnknownAPIError, -) -from suvvyapi.models.history import ChatHistory, Message -from suvvyapi.models.responses import Prediction - - -class SuvvyAPIWrapper: - @deprecation.deprecated( - deprecated_in="1.0.0", - removed_in="2.0.0", - details="Use the Suvvy() class instead", - ) - def __init__( - self, - token: str, - base_url: str = "https://api.suvvy.ai/", - check_connection: bool = True, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - ): - self.token = token - self.base_url = base_url.lstrip("/") - self.placeholders = placeholders or {} - self.custom_log_info = custom_log_info or {} - - if check_connection: - self._make_request("GET", "/api/check") - - def _make_request( - self, - method: Literal["GET", "POST", "PUT", "DELETE"], - path: str, - body: dict | None = None, - ) -> httpx.Response: - headers = {"Authorization": f"bearer {self.token}"} - with httpx.Client(headers=headers, base_url=self.base_url, timeout=300) as c: - response = c.request(method=method, url=path, json=body) - if response.status_code == 401: - raise InvalidAPITokenError("API Token is invalid.") - if response.status_code == 402: - raise NegativeBalanceError.from_detail(response.json()["detail"]) - if response.status_code == 500: - raise InternalAPIError( - "Internal API error occurred. Contact suvvy.ai support." - ) - return response - - def get_history(self, unique_id: str) -> ChatHistory: - response = self._make_request( - method="GET", path=f"/api/v1/history?unique_id={unique_id}" - ) - json = response.json() - history = ChatHistory(**json) - return history - - def reset_history(self, unique_id: str) -> None: - self._make_request(method="PUT", path=f"/api/v1/history?unique_id={unique_id}") - - def add_message( - self, - message: Message | list[Message], - unique_id: str, - pass_ai_as_employee: bool = True, - ) -> None: - if not isinstance(message, list): - message = [message] - - _ms = [] - for m in message: - _ms.append(m.model_dump()) - - message = _ms - - body = {"messages": message, "pass_ai_as_employee": pass_ai_as_employee} - self._make_request( - method="POST", - path=f"/api/v1/history/message?unique_id={unique_id}", - body=body, - ) - - def predict_from_history( - self, - unique_id: str, - placeholders: dict | None = None, - auto_insert_ai: bool = True, - custom_log_info: dict | None = None, - raise_if_dialog_stopped: bool = False, - ) -> Prediction: - placeholders = placeholders or {} - custom_log_info = custom_log_info or {} - - custom_log_info = dict(**self.custom_log_info, **custom_log_info) - placeholders = dict(**self.placeholders, **placeholders) - - body = { - "placeholders": placeholders, - "custom_log_info": custom_log_info, - "auto_insert_ai": auto_insert_ai, - } - response = self._make_request( - method="POST", - path=f"/api/v1/history/predict?unique_id={unique_id}", - body=body, - ) - match response.status_code: - case 202: - if raise_if_dialog_stopped: - raise HistoryStoppedError("History is marked as stopped") - else: - prediction = Prediction() - return prediction - case 404: - raise HistoryNotFoundError() - case 413: - json = response.json() - detail = json["detail"] - if detail.startswith("Maximum token limit"): - raise HistoryTooLongError("History is too long to process") - else: - raise MessageLimitExceededError( - "Message limit for that instance is exceeded" - ) - case 200: - pass - case _: - raise UnknownAPIError( - f"We don't know what happened. Status code is {response.status_code}" - ) - - json = response.json() - prediction = Prediction(**json) - return prediction - - def predict( - self, - message: Message | list[Message], - unique_id: str, - pass_ai_as_employee: bool = True, - placeholders: dict | None = None, - auto_insert_ai: bool = True, - custom_log_info: dict | None = None, - raise_if_dialog_stopped: bool = False, - ) -> Prediction: - placeholders = placeholders or {} - custom_log_info = custom_log_info or {} - - if not isinstance(message, list): - message = [message] - - _ms = [] - for m in message: - _ms.append(m.model_dump()) - - message = _ms - - custom_log_info = dict(**self.custom_log_info, **custom_log_info) - placeholders = dict(**self.placeholders, **placeholders) - - body = { - "messages": message, - "pass_ai_as_employee": pass_ai_as_employee, - "placeholders": placeholders, - "custom_log_info": custom_log_info, - "auto_insert_ai": auto_insert_ai, - } - - response = self._make_request( - method="POST", - path=f"/api/v1/history/message/predict?unique_id={unique_id}", - body=body, - ) - match response.status_code: - case 202: - if raise_if_dialog_stopped: - raise HistoryStoppedError("History is marked as stopped") - else: - prediction = Prediction() - return prediction - case 404: - raise HistoryNotFoundError() - case 413: - json = response.json() - detail = json["detail"] - if detail.startswith("Maximum token limit"): - raise HistoryTooLongError("History is too long to process") - else: - raise MessageLimitExceededError( - "Message limit for that instance is exceeded" - ) - case 200: - pass - case _: - raise UnknownAPIError( - f"We don't know what happened. Status code is {response.status_code}" - ) - - json = response.json() - prediction = Prediction(**json) - return prediction diff --git a/suvvyapi/exceptions/__init__.py b/suvvyapi/types/__init__.py similarity index 100% rename from suvvyapi/exceptions/__init__.py rename to suvvyapi/types/__init__.py diff --git a/suvvyapi/types/attachment.py b/suvvyapi/types/attachment.py new file mode 100644 index 0000000..beb1951 --- /dev/null +++ b/suvvyapi/types/attachment.py @@ -0,0 +1,94 @@ +import base64 +import string +from dataclasses import dataclass +from random import choices +from typing import Literal, BinaryIO + +import magic + +MIME_TO_EXTENSION: dict[str, str] = { + "audio/ogg": "ogg", + "audio/mpeg": "mp3", + "audio/mp4": "mp4", + "audio/wav": "wav", + "audio/webm": "webm", + "audio/x-wav": "wav", + "audio/m4a": "m4a", + "audio/x-m4a": "m4a", + "image/png": "png", + "image/jpeg": "jpeg", + "image/gif": "gif", +} + + +@dataclass +class Attachment: + """Attachment""" + file_name: str + file_type: Literal["image", "audio"] + data: str + + def to_dict(self) -> dict: + return { + "file_name": self.file_name, + "file_type": self.file_type, + "data": self.data, + } + + +def determine_attachment_type(mybytes: bytes) -> Literal["image", "audio"]: + mime_type = magic.from_buffer(mybytes, mime=True) + if mime_type in ["image/png", "image/jpeg", "image/gif"]: + return "image" + elif mime_type in ["audio/ogg", "audio/mpeg", "audio/wav", "audio/x-wav", "audio/webm", "audio/mp4", "audio/m4a", "audio/x-m4a"]: + return "audio" + else: + raise ValueError("Invalid file type") + + +def determine_extension(mybytes: bytes) -> str: + mime_type = magic.from_buffer(mybytes, mime=True) + return MIME_TO_EXTENSION[mime_type] + + +def input_to_attachments(input_list: list[Attachment | str | tuple[str, bytes] | BinaryIO]) -> list[Attachment]: + attachments: list[Attachment] = [] + for a in input_list: + if isinstance(a, Attachment): + attachments.append(a) + elif isinstance(a, str): + with open(a, "rb") as file: + data = file.read() + attachments.append( + Attachment( + file_name=file.name, + file_type=determine_attachment_type(data), + data=base64.b64encode(data).decode("utf-8"), + message_id="".join(choices(string.ascii_letters + string.digits, k=10)) + ) + ) + elif isinstance(a, tuple): + if not isinstance(a[0], str) or not isinstance(a[1], bytes): + raise ValueError("Invalid attachment") + + attachments.append( + Attachment( + file_name=a[0], + file_type=determine_attachment_type(a[1]), + data=base64.b64encode(a[1]).decode("utf-8"), + message_id="".join(choices(string.ascii_letters + string.digits, k=10)) + ) + ) + elif isinstance(a, BinaryIO): + data = a.read() + attachments.append( + Attachment( + file_name=a.name or "unknown", + file_type=determine_attachment_type(data), + data=base64.b64encode(data).decode("utf-8"), + message_id="".join(choices(string.ascii_letters + string.digits, k=10)) + ) + ) + else: + raise ValueError("Invalid attachment") + return attachments diff --git a/suvvyapi/types/enum.py b/suvvyapi/types/enum.py new file mode 100644 index 0000000..9d132a0 --- /dev/null +++ b/suvvyapi/types/enum.py @@ -0,0 +1,7 @@ +from enum import StrEnum + + +class SuvvyMessageSender(StrEnum): + CUSTOMER = "customer" + EMPLOYEE = "employee" + diff --git a/suvvyapi/types/exceptions.py b/suvvyapi/types/exceptions.py new file mode 100644 index 0000000..ed3c965 --- /dev/null +++ b/suvvyapi/types/exceptions.py @@ -0,0 +1,22 @@ +class SuvvyError(Exception): + """Error in Suvvy API""" + + +class InvalidToken(SuvvyError): + """Invalid Suvvy AI token""" + + +class CustomChannelDisabled(SuvvyError): + """Bot custom channel is disabled.""" + + +class NegativeBalance(SuvvyError): + """Your Suvvy balance is below zero.""" + + +class InvalidFileType(SuvvyError): + """File type is not supported or invalid.""" + + +class ValidationError(SuvvyError): + """Validation error.""" diff --git a/suvvyapi/types/link.py b/suvvyapi/types/link.py new file mode 100644 index 0000000..0c6f5d1 --- /dev/null +++ b/suvvyapi/types/link.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from typing import Literal + + +@dataclass +class Link: + """Link""" + url: str + hint: str | None = None + type: Literal["user", "chat", "phone", "lead", "other"] = "other" + + def to_dict(self) -> dict: + return { + "url": self.url, + "hint": self.hint, + "type": self.type + } diff --git a/suvvyapi/wrapper/__init__.py b/suvvyapi/wrapper/__init__.py deleted file mode 100644 index 023c3d6..0000000 --- a/suvvyapi/wrapper/__init__.py +++ /dev/null @@ -1,278 +0,0 @@ -from typing import Type - -import httpx - -from suvvyapi import ChatHistory, Message, Prediction -from suvvyapi.exceptions.api import ( - InvalidAPITokenError, - NegativeBalanceError, - HistoryTooLongError, - InternalAPIError, - HistoryNotFoundError, - InternalMessageAdded, -) - - -def _handle_error(response: httpx.Response) -> None: - if response.status_code <= 299: - return - - exceptions = { - 401: InvalidAPITokenError, - 402: NegativeBalanceError.from_detail, - 406: InternalMessageAdded, - 413: HistoryTooLongError, - 404: HistoryNotFoundError, - 500: InternalAPIError, - } - exception: Type[BaseException] = exceptions[response.status_code] # type: ignore - raise exception(response.json().get("detail", None)) - - -class Suvvy(object): - def __init__( - self, - api_token: str, - api_url: str = "https://api.suvvy.ai", - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ): - self.placeholders = placeholders or {} - self.custom_log_info = custom_log_info or {} - self.source = source or "https://github.com/suvvyai/suvvyapi" - - self._headers = {"Authorization": f"Bearer {api_token}"} - self._api_url = api_url.rstrip("/ \\\n") - - def _get_placeholders(self, placeholders: dict | None = None) -> dict: - placeholders = placeholders or {} - return {**self.placeholders, **placeholders} - - def _get_custom_log_info(self, custom_log_info: dict | None = None) -> dict: - custom_log_info = custom_log_info or {} - return {**self.custom_log_info, **custom_log_info} - - def _sync_request( - self, - method: str, - path: str, - body_json: dict | None = None, - params: dict | None = None, - ) -> httpx.Response: - with httpx.Client( - headers=self._headers, base_url=self._api_url, timeout=300 - ) as client: - r = client.request(method, path, json=body_json, params=params) - _handle_error(r) - return r - - async def _async_request( - self, - method: str, - path: str, - body_json: dict | None = None, - params: dict | None = None, - ) -> httpx.Response: - async with httpx.AsyncClient( - headers=self._headers, base_url=self._api_url, timeout=300 - ) as client: - r = await client.request(method, path, json=body_json, params=params) - _handle_error(r) - return r - - def check_connection(self) -> bool: - """Check connection and API token""" - self._sync_request("GET", "/api/check") - return True - - async def acheck_connection(self) -> bool: - """Check connection and API token""" - await self._async_request("GET", "/api/check") - return True - - def get_history(self, unique_id: str) -> ChatHistory: - """Get history by unique_id""" - r = self._sync_request( - "GET", "/api/v1/history", params={"unique_id": unique_id} - ) - return ChatHistory(**r.json()) - - async def aget_history(self, unique_id: str) -> ChatHistory: - """Get history by unique_id""" - r = await self._async_request( - "GET", "/api/v1/history", params={"unique_id": unique_id} - ) - return ChatHistory(**r.json()) - - def reset_history(self, unique_id: str) -> ChatHistory: - """Reset history by unique_id and return deleted history""" - r = self._sync_request( - "PUT", "/api/v1/history", params={"unique_id": unique_id} - ) - if r.status_code == 202: - raise HistoryNotFoundError - return ChatHistory(**r.json()["deleted_history"]) - - async def areset_history(self, unique_id: str) -> ChatHistory: - """Reset history by unique_id and return deleted history""" - r = await self._async_request( - "PUT", "/api/v1/history", params={"unique_id": unique_id} - ) - if r.status_code == 202: - raise HistoryNotFoundError - return ChatHistory(**r.json()["deleted_history"]) - - def add_message_to_history( - self, unique_id: str, message: list[Message] | Message - ) -> ChatHistory: - """Add message to history by unique_id""" - if not isinstance(message, list): - message = [message] - message = [m.model_dump() for m in message] - - r = self._sync_request( - "POST", - "/api/v1/history/message", - params={"unique_id": unique_id}, - body_json={"messages": message}, - ) - return ChatHistory(**r.json()) - - async def async_add_message_to_history( - self, unique_id: str, message: list[Message] | Message - ) -> ChatHistory: - """Add message to history by unique_id""" - if not isinstance(message, list): - message = [message] - message = [m.model_dump() for m in message] - - r = await self._async_request( - "POST", - "/api/v1/history/message", - params={"unique_id": unique_id}, - body_json={"messages": message}, - ) - return ChatHistory(**r.json()) - - def predict_history( - self, - unique_id: str, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Get answer from AI by unique_id. - None means API refused to answer""" - - r = self._sync_request( - method="POST", - path="/api/v1/history/predict", - params={"unique_id": unique_id}, - body_json={ - "placeholders": self._get_placeholders(placeholders), - "custom_log_info": self._get_custom_log_info(custom_log_info), - "source": source or self.source, - }, - ) - - if r.status_code == 202: - return None - - return Prediction(**r.json()) - - async def apredict_history( - self, - unique_id: str, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Get answer from AI by unique_id. - None means API refused to answer""" - - r = await self._async_request( - method="POST", - path="/api/v1/history/predict", - params={"unique_id": unique_id}, - body_json={ - "placeholders": self._get_placeholders(placeholders), - "custom_log_info": self._get_custom_log_info(custom_log_info), - "source": source or self.source, - }, - ) - - if r.status_code == 202: - return None - - return Prediction(**r.json()) - - def predict_history_add_message( - self, - unique_id: str, - message: list[Message] | Message, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Add message and get answer from AI by unique_id. - None means API refused to answer""" - - if not isinstance(message, list): - message = [message] - message = [m.model_dump() for m in message] - - r = self._sync_request( - method="POST", - path="/api/v1/history/message/predict", - params={"unique_id": unique_id}, - body_json={ - "placeholders": self._get_placeholders(placeholders), - "custom_log_info": self._get_custom_log_info(custom_log_info), - "source": source or self.source, - "messages": message, - }, - ) - - if r.status_code == 202: - return None - - return Prediction(**r.json()) - - async def apredict_history_add_message( - self, - unique_id: str, - message: list[Message] | Message, - placeholders: dict | None = None, - custom_log_info: dict | None = None, - source: str | None = None, - ) -> Prediction | None: - """Add message and get answer from AI by unique_id. - None means API refused to answer""" - - if not isinstance(message, list): - message = [message] - message = [m.model_dump() for m in message] - - r = await self._async_request( - method="POST", - path="/api/v1/history/message/predict", - params={"unique_id": unique_id}, - body_json={ - "placeholders": self._get_placeholders(placeholders), - "custom_log_info": self._get_custom_log_info(custom_log_info), - "source": source or self.source, - "messages": message, - }, - ) - - if r.status_code == 202: - return None - - return Prediction(**r.json()) - - def as_history(self, unique_id: str) -> "History": # type: ignore - """Represent history as a History object""" - from suvvyapi.history import History - - return History(unique_id, self) # type: ignore diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index a4b22c8..0000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,128 +0,0 @@ -# mypy: ignore_errors -import datetime -import os -import random -import string - -from devtools import debug - -from suvvyapi import Suvvy, Message - -suvvy = Suvvy(os.getenv("TEST1_SUVVY_TOKEN"), api_url="https://test.api.suvvy.ai") - - -def generate_unique_id() -> str: - """Generate a unique dialog ID""" - return ( - f"pytest-" - f"{datetime.datetime.utcnow().isoformat()}-" - f"{''.join(random.choices(string.digits + string.ascii_letters, k=50))}" - ) - - -unique_id = generate_unique_id() -async_unique_id = generate_unique_id() - - -def test_check_connection(): - assert suvvy.check_connection() - - -async def test_async_check_connection(): - assert await suvvy.acheck_connection() - - -def test_add_message(): - r = suvvy.add_message_to_history( - unique_id, - Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!"), - ) - debug(r) - - -async def test_async_add_message(): - r = await suvvy.async_add_message_to_history( - async_unique_id, - Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!"), - ) - debug(r) - - -def test_predict_history(): - r = suvvy.predict_history(unique_id) - assert r is not None - debug(r) - - -async def test_apredict_history(): - r = await suvvy.apredict_history(async_unique_id) - assert r is not None - debug(r) - - -def test_predict_history_refuse_to_answer(): - suvvy.add_message_to_history( - unique_id, - Message(text="Π₯ΠΎΡ€ΠΎΡˆΠΎ, ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ ΠΌΠΈΠ½ΡƒΡ‚ΠΊΡƒ"), - ) - r = suvvy.predict_history(unique_id) - assert r is None - - -async def test_apredict_history_refuse_to_answer(): - await suvvy.async_add_message_to_history( - async_unique_id, - Message(text="Π₯ΠΎΡ€ΠΎΡˆΠΎ, ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ ΠΌΠΈΠ½ΡƒΡ‚ΠΊΡƒ"), - ) - r = await suvvy.apredict_history(async_unique_id) - assert r is None - - -def test_get_history(): - r = suvvy.get_history(unique_id) - debug(r) - - -def test_reset_history(): - r = suvvy.reset_history(unique_id) - debug(r) - - -async def test_aget_history(): - r = await suvvy.aget_history(async_unique_id) - debug(r) - - -async def test_areset_history(): - r = await suvvy.areset_history(async_unique_id) - debug(r) - - -def test_predict_history_add_message(): - r = suvvy.predict_history_add_message(unique_id, Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!")) - assert r is not None - debug(r) - - -async def test_apredict_history_add_message(): - r = await suvvy.apredict_history_add_message( - async_unique_id, Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!") - ) - assert r is not None - debug(r) - - -def test_predict_history_add_message_refuse_to_answer(): - r = suvvy.predict_history_add_message( - unique_id, - Message(text="Π₯ΠΎΡ€ΠΎΡˆΠΎ, ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ ΠΌΠΈΠ½ΡƒΡ‚ΠΊΡƒ"), - ) - assert r is None - - -async def test_apredict_history_add_message_refuse_to_answer(): - r = await suvvy.apredict_history_add_message( - async_unique_id, - Message(text="Π₯ΠΎΡ€ΠΎΡˆΠΎ, ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ ΠΌΠΈΠ½ΡƒΡ‚ΠΊΡƒ"), - ) - assert r is None diff --git a/tests/test_history.py b/tests/test_history.py deleted file mode 100644 index d72fa52..0000000 --- a/tests/test_history.py +++ /dev/null @@ -1,77 +0,0 @@ -# mypy: ignore_errors -import datetime -import os -import random -import string - -from devtools import debug - -from suvvyapi import Suvvy, Message - -suvvy = Suvvy(os.getenv("TEST1_SUVVY_TOKEN"), api_url="https://test.api.suvvy.ai") - - -def generate_unique_id() -> str: - """Generate a unique dialog ID""" - return ( - f"pytest-" - f"{datetime.datetime.utcnow().isoformat()}-" - f"{''.join(random.choices(string.digits + string.ascii_letters, k=50))}" - ) - - -unique_id = generate_unique_id() -async_unique_id = generate_unique_id() - -history = suvvy.as_history(unique_id) -async_history = suvvy.as_history(async_unique_id) - - -def test_get(): - r = history.get() - debug(r) - - -async def test_aget(): - r = await async_history.aget() - debug(r) - - -def test_add_message(): - r = history.add_message(Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!")) - debug(r) - - -async def test_async_add_message(): - r = await async_history.async_add_message(Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!")) - debug(r) - - -def test_predict(): - r = history.predict() - debug(r) - - -async def test_apredict(): - r = await async_history.apredict() - debug(r) - - -def test_reset(): - r = history.reset() - debug(r) - - -async def test_areset(): - r = await async_history.areset() - debug(r) - - -def test_predict_add_message(): - r = history.predict_add_message(Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!")) - debug(r) - - -async def test_apredict_add_message(): - r = await async_history.apredict_add_message(Message(text="ΠŸΡ€ΠΈΠ²Π΅Ρ‚!")) - debug(r)